Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
2
2020-101
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Sachith Fernando
2020-101
Commits
006dc258
Commit
006dc258
authored
Oct 02, 2020
by
SohanDanushka
Committed by
I.K Seneviratne
Oct 19, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
db changes and lecturer video
parent
b03380b0
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
151 additions
and
40 deletions
+151
-40
MonitorLecturerApp/api.py
MonitorLecturerApp/api.py
+55
-2
MonitorLecturerApp/models.py
MonitorLecturerApp/models.py
+14
-10
MonitorLecturerApp/serializers.py
MonitorLecturerApp/serializers.py
+3
-0
MonitorLecturerApp/templates/MonitorLecturerApp/index.html
MonitorLecturerApp/templates/MonitorLecturerApp/index.html
+50
-22
MonitorLecturerApp/urls.py
MonitorLecturerApp/urls.py
+7
-0
MonitorLecturerApp/views.py
MonitorLecturerApp/views.py
+22
-6
No files found.
MonitorLecturerApp/api.py
View file @
006dc258
...
@@ -4,23 +4,67 @@ from rest_framework.response import Response
...
@@ -4,23 +4,67 @@ from rest_framework.response import Response
from
LectureSummarizingApp.models
import
LectureAudioSummary
from
LectureSummarizingApp.models
import
LectureAudioSummary
from
LectureSummarizingApp.serializer
import
LectureAudioSummarySerializer
from
LectureSummarizingApp.serializer
import
LectureAudioSummarySerializer
from
.
logic
import
classroom_activity
,
text_analysis
as
ta
from
.
logic
import
classroom_activity
,
text_analysis
as
ta
from
.models
import
LecturerVideo
,
LecturerAudioText
from
.models
import
LecturerVideo
,
LecturerAudioText
,
LecturerVideoMetaData
,
LectureRecordedVideo
from
.serializers
import
LecturerVideoSerializer
,
LecturerAudioTextSerializer
from
.serializers
import
*
import
datetime
import
datetime
##### LECTURER ACTIVITY SECTION #####
class
ActivityRecognitionAPI
(
APIView
):
class
ActivityRecognitionAPI
(
APIView
):
def
get
(
self
,
request
):
def
get
(
self
,
request
):
video_name
=
request
.
query_params
.
get
(
'video_name'
)
video_name
=
request
.
query_params
.
get
(
'video_name'
)
# retrieve the lecturer video details with the video name
lec_video
=
LectureRecordedVideo
.
objects
.
filter
(
lecture_video_name
=
video_name
)
lec_video_ser
=
LectureRecordedVideoSerializer
(
lec_video
,
many
=
True
)
lec_video_data
=
lec_video_ser
.
data
percentages
=
classroom_activity
.
activity_recognition
(
video_name
)
percentages
=
classroom_activity
.
activity_recognition
(
video_name
)
# saving to the db
LecturerVideoMetaData
(
lecturer_video_id_id
=
lec_video_data
[
0
][
'id'
],
seated_count
=
percentages
[
"sitting_perct"
],
standing_count
=
percentages
[
"standing_perct"
],
walking_count
=
percentages
[
"walking_perct"
]
)
.
save
()
return
Response
({
"response"
:
percentages
})
return
Response
({
"response"
:
percentages
})
def
post
(
self
,
request
):
def
post
(
self
,
request
):
pass
pass
# this method will retrieve the lecturer video meta data results
class
GetLectureVideoResultsAPI
(
APIView
):
def
get
(
self
,
request
):
video_id
=
request
.
query_params
.
get
(
'video_id'
)
int_video_id
=
int
(
video_id
)
# retrieve from the db
video_meta_data
=
LecturerVideoMetaData
.
objects
.
filter
(
lecturer_video_id_id
=
int_video_id
)
video_meta_data_ser
=
LecturerVideoMetaDataSerializer
(
video_meta_data
,
many
=
True
)
video_meta_data_processed
=
video_meta_data_ser
.
data
percentages
=
{}
for
meta_data
in
video_meta_data_processed
:
percentages
[
"sitting_perct"
]
=
meta_data
[
"seated_count"
]
percentages
[
"standing_perct"
]
=
meta_data
[
"standing_count"
]
percentages
[
"walking_perct"
]
=
meta_data
[
"walking_count"
]
return
Response
({
"response"
:
percentages
})
##### END OF LECTURER ACTIVITY SECTION #####
# class ActivityRecognitionAPI(APIView):
# class ActivityRecognitionAPI(APIView):
#
#
# def get(self, request):
# def get(self, request):
...
@@ -39,11 +83,17 @@ class GetLectureAudioAnalysis(APIView):
...
@@ -39,11 +83,17 @@ class GetLectureAudioAnalysis(APIView):
lec_audio_id
=
request
.
query_params
.
get
(
"audio_id"
)
lec_audio_id
=
request
.
query_params
.
get
(
"audio_id"
)
int_audio_id
=
int
(
lec_audio_id
)
int_audio_id
=
int
(
lec_audio_id
)
print
(
'audio id: '
,
int_audio_id
)
# all_lec_audio_summary
lec_audio_summary
=
LectureAudioSummary
.
objects
.
filter
(
lecture_audio_id
=
int_audio_id
)
lec_audio_summary
=
LectureAudioSummary
.
objects
.
filter
(
lecture_audio_id
=
int_audio_id
)
lec_audio_summary_serializer
=
LectureAudioSummarySerializer
(
lec_audio_summary
,
many
=
True
)
lec_audio_summary_serializer
=
LectureAudioSummarySerializer
(
lec_audio_summary
,
many
=
True
)
audio_summary_data
=
lec_audio_summary_serializer
.
data
audio_summary_data
=
lec_audio_summary_serializer
.
data
lec_audio_summary_id
=
0
lec_audio_summary_id
=
0
print
(
'lec audio summary: '
,
len
(
audio_summary_data
))
for
audio
in
audio_summary_data
:
for
audio
in
audio_summary_data
:
lec_audio_summary_id
=
audio
[
'id'
]
lec_audio_summary_id
=
audio
[
'id'
]
...
@@ -52,6 +102,7 @@ class GetLectureAudioAnalysis(APIView):
...
@@ -52,6 +102,7 @@ class GetLectureAudioAnalysis(APIView):
lec_audio_text_serializer
=
LecturerAudioTextSerializer
(
lec_audio_text
,
many
=
True
)
lec_audio_text_serializer
=
LecturerAudioTextSerializer
(
lec_audio_text
,
many
=
True
)
lec_audio_text_data
=
lec_audio_text_serializer
.
data
lec_audio_text_data
=
lec_audio_text_serializer
.
data
print
(
'lec audio text data: '
,
len
(
lec_audio_text_data
))
audio_text
=
[]
audio_text
=
[]
...
@@ -157,3 +208,5 @@ class LecturerAudioSummaryPeriodAPI(APIView):
...
@@ -157,3 +208,5 @@ class LecturerAudioSummaryPeriodAPI(APIView):
"labels"
:
labels
,
"labels"
:
labels
,
"isRecordFound"
:
isRecordFound
"isRecordFound"
:
isRecordFound
})
})
MonitorLecturerApp/models.py
View file @
006dc258
...
@@ -29,7 +29,21 @@ class LecturerVideo(models.Model):
...
@@ -29,7 +29,21 @@ class LecturerVideo(models.Model):
return
self
.
name
return
self
.
name
class
LectureRecordedVideo
(
models
.
Model
):
lecture_video_id
=
models
.
CharField
(
max_length
=
10
)
lecturer_date
=
models
.
DateField
()
lecture_video_name
=
models
.
CharField
(
max_length
=
50
)
lecture_video_length
=
models
.
DurationField
()
lecturer
=
models
.
ForeignKey
(
Lecturer
,
on_delete
=
models
.
CASCADE
,
default
=
0
)
subject
=
models
.
ForeignKey
(
Subject
,
on_delete
=
models
.
CASCADE
,
default
=
0
)
def
__str__
(
self
):
return
self
.
lecture_video_id
class
LecturerVideoMetaData
(
models
.
Model
):
class
LecturerVideoMetaData
(
models
.
Model
):
lecturer_video_id
=
models
.
ForeignKey
(
LectureRecordedVideo
,
on_delete
=
models
.
CASCADE
,
default
=
0
)
fps
=
models
.
IntegerField
()
fps
=
models
.
IntegerField
()
frame_count
=
models
.
IntegerField
()
frame_count
=
models
.
IntegerField
()
seated_count
=
models
.
IntegerField
()
seated_count
=
models
.
IntegerField
()
...
@@ -73,13 +87,3 @@ class LecturerAudioText (models.Model):
...
@@ -73,13 +87,3 @@ class LecturerAudioText (models.Model):
def
__str__
(
self
):
def
__str__
(
self
):
return
self
.
lecturer_audio_text_id
return
self
.
lecturer_audio_text_id
class
LectureRecordedVideo
(
models
.
Model
):
lecture_video_id
=
models
.
CharField
(
max_length
=
10
)
lecturer_date
=
models
.
DateField
()
lecture_video_name
=
models
.
CharField
(
max_length
=
50
)
lecture_video_length
=
models
.
DurationField
()
lecturer
=
models
.
ForeignKey
(
Lecturer
,
on_delete
=
models
.
CASCADE
,
default
=
0
)
subject
=
models
.
ForeignKey
(
Subject
,
on_delete
=
models
.
CASCADE
,
default
=
0
)
def
__str__
(
self
):
return
self
.
lecture_video_id
\ No newline at end of file
MonitorLecturerApp/serializers.py
View file @
006dc258
...
@@ -18,6 +18,9 @@ class LecturerVideoSerializer(serializers.ModelSerializer):
...
@@ -18,6 +18,9 @@ class LecturerVideoSerializer(serializers.ModelSerializer):
fields
=
'__all__'
fields
=
'__all__'
class
LecturerVideoMetaDataSerializer
(
serializers
.
ModelSerializer
):
class
LecturerVideoMetaDataSerializer
(
serializers
.
ModelSerializer
):
lecturer_video_id
=
LectureRecordedVideo
()
class
Meta
:
class
Meta
:
model
=
LecturerVideoMetaData
model
=
LecturerVideoMetaData
fields
=
'__all__'
fields
=
'__all__'
...
...
MonitorLecturerApp/templates/MonitorLecturerApp/index.html
View file @
006dc258
...
@@ -40,28 +40,53 @@
...
@@ -40,28 +40,53 @@
$
(
document
).
ready
(
function
()
{
$
(
document
).
ready
(
function
()
{
$
(
'
.calc
'
).
click
(
function
()
{
$
(
'
.calc
'
).
click
(
function
()
{
let
video_name
=
$
(
this
).
attr
(
'
id
'
);
let
video_name
=
$
(
this
).
attr
(
'
data-name
'
);
$
(
'
#loader
'
).
attr
(
'
hidden
'
,
false
);
$
(
'
#no_content_message
'
).
attr
(
'
hidden
'
,
true
);
$
(
'
#video_loader
'
).
attr
(
'
hidden
'
,
false
);
// alert('hello');
// alert('hello');
$
(
'
#no_content_message
'
).
attr
(
'
hidden
'
,
true
);
//fetching data from the API
//fetching data from the API
fetch
(
'
http://127.0.0.1:8000/lecturer/activities/?video_name=
'
+
video_name
)
fetch
(
'
http://127.0.0.1:8000/lecturer/activities/?video_name=
'
+
video_name
)
.
then
((
res
)
=>
res
.
json
())
.
then
((
res
)
=>
res
.
json
())
.
then
((
out
)
=>
assignPerct
(
out
.
response
))
.
then
((
out
)
=>
assignPerct
(
out
.
response
))
.
catch
((
error
)
=>
alert
(
'
error
'
+
error
))
.
catch
((
error
)
=>
alert
(
'
error
'
+
error
));
});
//this function will handle the lecturer video results button
$
(
'
.results
'
).
click
(
function
()
{
let
video_id
=
$
(
this
).
attr
(
'
data-id
'
);
$
(
'
#no_content_message
'
).
attr
(
'
hidden
'
,
true
);
$
(
'
#video_loader
'
).
attr
(
'
hidden
'
,
false
);
//fetch the results
fetch
(
'
http://127.0.0.1:8000/lecturer/get-lecturer-video-results/?video_id=
'
+
video_id
)
.
then
((
res
)
=>
res
.
json
())
.
then
((
out
)
=>
assignPerct
(
out
.
response
))
.
catch
((
err
)
=>
alert
(
'
error:
'
+
err
))
});
});
//to assign percentage values
//to assign percentage values
function
assignPerct
(
percentages
)
{
function
assignPerct
(
percentages
)
{
$
(
'
#no_content_message
'
).
attr
(
'
hidden
'
,
true
);
$
(
'
#no_content_message
'
).
attr
(
'
hidden
'
,
true
);
$
(
'
#progress_bars
'
).
attr
(
'
hidden
'
,
false
);
$
(
'
#progress_bars
'
).
attr
(
'
hidden
'
,
false
);
$
(
'
#loader
'
).
attr
(
'
hidden
'
,
true
);
$
(
'
#loader
'
).
attr
(
'
hidden
'
,
true
);
{
#
let
sitting
=
Math
.
round
(
percentages
.
sitting_perct
);
#
}
{
#
let
sitting
=
Math
.
round
(
percentages
.
sitting_perct
);
#
}
let
sitting
=
Math
.
round
(
percentages
.
sitting_perct
);
let
sitting
=
Math
.
round
(
percentages
.
sitting_perct
);
let
standing
=
Math
.
round
(
percentages
.
standing_perct
);
let
standing
=
Math
.
round
(
percentages
.
standing_perct
);
...
@@ -75,9 +100,10 @@
...
@@ -75,9 +100,10 @@
$
(
'
#standing_span
'
).
text
(
standing
+
'
%
'
);
$
(
'
#standing_span
'
).
text
(
standing
+
'
%
'
);
$
(
'
#walking_span
'
).
text
(
walking
+
'
%
'
);
$
(
'
#walking_span
'
).
text
(
walking
+
'
%
'
);
$
(
'
#video_loader
'
).
hide
();
}
}
/*
/*
//this is for the temporay button (delete later)
//this is for the temporay button (delete later)
$('#temp_btn').click(function () {
$('#temp_btn').click(function () {
...
@@ -443,8 +469,8 @@
...
@@ -443,8 +469,8 @@
<!-- Card Header - Dropdown -->
<!-- Card Header - Dropdown -->
<div
class=
"card-header py-3 d-flex flex-row align-items-center justify-content-between"
>
<div
class=
"card-header py-3 d-flex flex-row align-items-center justify-content-between"
>
<h6
class=
"m-0 font-weight-bold text-primary"
>
Video List
</h6>
<h6
class=
"m-0 font-weight-bold text-primary"
>
Video List
</h6>
{#
<button
type=
"button"
class=
"btn btn-outline-primary"
id=
"video_graph"
>
Statistics#}
<button
type=
"button"
class=
"btn btn-outline-primary"
id=
"video_graph"
>
Statistics
{#
</button>
#}
</button>
</div>
</div>
<!-- Card Body -->
<!-- Card Body -->
...
@@ -463,11 +489,21 @@
...
@@ -463,11 +489,21 @@
<tr>
<tr>
<td>
{{ video.name }}
</td>
<td>
{{ video.name }}
</td>
<td>
{{ video.duration }}
</td>
<td>
{{ video.duration }}
</td>
<td
class=
"btn_class"
>
{% if video.isAvailable %}
<td
class=
"btn_class"
>
<button
type=
"button"
class=
"btn btn-primary results"
data-name=
'{{ video.name }}'
data-id=
"{{ video.video_id }}"
>
Results
</button>
</td>
{% else %}
<td
class=
"btn_class"
>
<button
type=
"button"
class=
"btn btn-success calc"
<button
type=
"button"
class=
"btn btn-success calc"
id
=
'{{ video.name }}'
>
Calculate
data-name
=
'{{ video.name }}'
>
Calculate
</button>
</button>
</td>
</td>
{% endif %}
</tr>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</tbody>
...
@@ -498,7 +534,6 @@
...
@@ -498,7 +534,6 @@
<!--loader -->
<!--loader -->
<div
class=
"text-center"
id=
"audio_loader"
hidden
>
<div
class=
"text-center"
id=
"audio_loader"
hidden
>
{#
<img
src=
"{% static 'FirstApp/images/ajax-loader.gif' %}"
alt=
""
>
#}
<img
src=
"{% static 'FirstApp/images/ajax-loader.gif' %}"
alt=
""
>
<img
src=
"{% static 'FirstApp/images/ajax-loader.gif' %}"
alt=
""
>
...
@@ -515,22 +550,15 @@
...
@@ -515,22 +550,15 @@
<!-- table body-->
<!-- table body-->
<tbody>
<tbody>
<tr>
<tr>
<td>
No of Total words
</td>
<td>
No of Total words
:
</td>
<td
id=
"num_of_words"
></td>
<td
id=
"num_of_words"
></td>
</tr>
</tr>
<tr>
<tr>
<td>
No
fo word to be expected
</td>
<td>
No
of word expected:
</td>
<td>
3600 - 4800
</td>
<td>
3600 - 4800
</td>
</tr>
</tr>
{#
<tr>
#}
{#
<td>
No of extraneous words
</td>
#}
{#
<td
id=
"lexical_count"
></td>
#}
{#
</tr>
#}
{#
<tr>
#}
{#
<td>
No of Non-lexical words
</td>
#}
{#
<td
id=
"non_lexical_count"
></td>
#}
{#
</tr>
#}
</tbody>
</tbody>
</table>
</table>
<!--button -->
<!--button -->
...
@@ -676,11 +704,11 @@
...
@@ -676,11 +704,11 @@
<table
class=
"table table-borderless"
>
<table
class=
"table table-borderless"
>
<tbody>
<tbody>
<tr>
<tr>
<td>
No. of extraneous words
</td>
<td>
No. of extraneous words
:
</td>
<td
id=
"lexical_count"
></td>
<td
id=
"lexical_count"
></td>
</tr>
</tr>
<tr>
<tr>
<td>
No. of non-lexical words
</td>
<td>
No. of non-lexical words
:
</td>
<td
id=
"non_lexical_count"
></td>
<td
id=
"non_lexical_count"
></td>
</tr>
</tr>
</tbody>
</tbody>
...
...
MonitorLecturerApp/urls.py
View file @
006dc258
...
@@ -24,9 +24,16 @@ urlpatterns = [
...
@@ -24,9 +24,16 @@ urlpatterns = [
path
(
'lecture-video'
,
views
.
lecVideo
),
path
(
'lecture-video'
,
views
.
lecVideo
),
# path('Video', views.hello)
# path('Video', views.hello)
##### 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
()),
# API to retrieve lecturer video meta data results
url
(
r'^get-lecturer-video-results/$'
,
api
.
GetLectureVideoResultsAPI
.
as_view
()),
##### END OF LECTURER ACTIVITY 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
()),
...
...
MonitorLecturerApp/views.py
View file @
006dc258
...
@@ -8,8 +8,8 @@ from rest_framework.response import Response
...
@@ -8,8 +8,8 @@ from rest_framework.response import Response
from
LectureSummarizingApp.models
import
LectureAudio
from
LectureSummarizingApp.models
import
LectureAudio
from
LectureSummarizingApp.serializer
import
LectureAudioSerializer
from
LectureSummarizingApp.serializer
import
LectureAudioSerializer
from
.
import
views
from
.
import
views
from
.models
import
RegisterTeacher
,
LecturerVideo
from
.models
import
*
from
.
serializers
import
RegisterTeacherSerializer
from
.
serializers
import
*
import
cv2
import
cv2
import
os
import
os
...
@@ -70,8 +70,14 @@ def hello(request):
...
@@ -70,8 +70,14 @@ def hello(request):
# the list needs to be sorted by the date
# the list needs to be sorted by the date
lec_list
.
sort
(
key
=
lambda
date
:
dt
.
strptime
(
str
(
date
[
'date'
]),
"
%
Y-
%
m-
%
d"
),
reverse
=
True
)
lec_list
.
sort
(
key
=
lambda
date
:
dt
.
strptime
(
str
(
date
[
'date'
]),
"
%
Y-
%
m-
%
d"
),
reverse
=
True
)
# retrieve exsiting lecture recorded videos
lec_recorded_video
=
LectureRecordedVideo
.
objects
.
all
()
lec_recorded_video_ser
=
LectureRecordedVideoSerializer
(
lec_recorded_video
,
many
=
True
)
lec_recorded_video_data
=
lec_recorded_video_ser
.
data
for
videoPath
in
videoPaths
:
for
videoPath
in
videoPaths
:
video
=
LecturerVideo
()
video
=
LecturerVideo
()
video
=
{}
cap
=
cv2
.
VideoCapture
(
videoPath
)
cap
=
cv2
.
VideoCapture
(
videoPath
)
fps
=
cap
.
get
(
cv2
.
CAP_PROP_FPS
)
# OpenCV2 version 2 used "CV_CAP_PROP_FPS"
fps
=
cap
.
get
(
cv2
.
CAP_PROP_FPS
)
# OpenCV2 version 2 used "CV_CAP_PROP_FPS"
frame_count
=
int
(
cap
.
get
(
cv2
.
CAP_PROP_FRAME_COUNT
))
frame_count
=
int
(
cap
.
get
(
cv2
.
CAP_PROP_FRAME_COUNT
))
...
@@ -80,11 +86,21 @@ def hello(request):
...
@@ -80,11 +86,21 @@ def hello(request):
videoName
=
os
.
path
.
basename
(
videoPath
)
videoName
=
os
.
path
.
basename
(
videoPath
)
# videoName = videos.append(os.path.basename(videoPath))
# videoName = videos.append(os.path.basename(videoPath))
durationObj
=
datetime
.
timedelta
(
seconds
=
duration
)
durationObj
=
datetime
.
timedelta
(
seconds
=
duration
)
video
.
path
=
videoPath
video
[
'path'
]
=
videoPath
video
.
name
=
videoName
video
[
'name'
]
=
videoName
video
.
duration
=
str
(
durationObj
)
video
[
'duration'
]
=
str
(
durationObj
)
video
[
'video_id'
]
=
None
# checking whether this video already exists
for
recorded_lecture
in
lec_recorded_video_data
:
if
videoName
==
recorded_lecture
[
'lecture_video_name'
]:
video
[
'isAvailable'
]
=
True
video
[
'video_id'
]
=
recorded_lecture
[
'id'
]
videos
.
append
(
video
)
videos
.
append
(
video
)
print
(
'Video Name: '
,
video
.
name
)
print
(
'Video Name: '
,
video
[
'name'
]
)
context
=
{
'object'
:
obj
,
'Videos'
:
videos
,
'durations'
:
durations
,
'template_name'
:
'MonitorLecturerApp/template.html'
,
'lec_list'
:
lec_list
}
context
=
{
'object'
:
obj
,
'Videos'
:
videos
,
'durations'
:
durations
,
'template_name'
:
'MonitorLecturerApp/template.html'
,
'lec_list'
:
lec_list
}
return
render
(
request
,
'MonitorLecturerApp/index.html'
,
context
)
return
render
(
request
,
'MonitorLecturerApp/index.html'
,
context
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment