Private
Public Access
1
0

VideoAnalysis object urls

This commit is contained in:
Sander Roosendaal
2019-11-05 22:09:26 +01:00
parent 0d48fae3cf
commit 8fdca8a1f6
7 changed files with 285 additions and 37 deletions

View File

@@ -3721,3 +3721,12 @@ class BlogPost(models.Model):
title = models.TextField(max_length=300) title = models.TextField(max_length=300)
link = models.TextField(max_length=300) link = models.TextField(max_length=300)
date = models.DateField() date = models.DateField()
class VideoAnalysis(models.Model):
name = models.CharField(default='', max_length=150,blank=True,null=True)
video_id = models.CharField(default='',max_length=150)
delay = models.IntegerField(default=0)
workout = models.ForeignKey(Workout, on_delete=models.CASCADE)
def __str__(self):
return self.name

View File

@@ -15,25 +15,10 @@
{% block main %} {% block main %}
<p>
{% if workout|previousworkout:rower.user %}
<a href="/rowers/workout/{{ workout|previousworkout:rower.user }}/stats"
title="Jump to preceding workout"><em>Previous</em></a>&nbsp;
{% endif %}
{% if workout|nextworkout:rower.user %}
<a href="/rowers/workout/{{ workout|nextworkout:rower.user }}/stats"
title="Jump to following workout"><em>Next</em></a>
{% endif %}
</p>
{% language 'en' %} {% language 'en' %}
<h1>Workout Video for {{ workout.name }}</h1> <h1>Video Analysis for {{ workout.name }}</h1>
<ul class="main-content"> <ul class="main-content">
<li class="grid_4">
<p>
This page will contain an embedded video
</p>
</li>
<li class="grid_4"> <li class="grid_4">
<div style="height:100%" id="theplot" class="flexplot mapdiv"> <div style="height:100%" id="theplot" class="flexplot mapdiv">
{{ mapdiv | safe}} {{ mapdiv | safe}}
@@ -126,21 +111,36 @@
</script> </script>
</li> </li>
<li class="grid_4"> <li class="grid_4">
{% if form %}
{% if not video_id %} {% if not video_id %}
<p> <p>
To load your video, paste the URL of your YouTube video in the form below, To load your video, paste the URL of your YouTube video in the form below,
and submit the form. and submit the form.
</p> </p>
{% else %}
<p>
Change parameters in the form and press Reload to see the result. When
you are ready to permanently save the video analysis, press Save.
</p>
{% endif %} {% endif %}
<p> <p>
<form enctype="multipart/form-data" action="" method="post"> <form enctype="multipart/form-data" action="" method="post">
<table> <table>
{{ form.as_table }} {{ form.as_table }}
</table> </table>
{% csrf_token %} {% csrf_token %}
<input type="submit" value="submit"> <input type="submit" name="reload_button" value="Reload">
</form> <input type="submit" name="save_button" value="Save">
</form>
</p> </p>
{% endif %}
</li>
<li>
{% if analysis and user.is_authenticated and user == rower.user %}
<p>
<a href="/rowers/video/{{ analysis.id }}/delete/">Delete Analysis</a>
</p>
{% endif %}
</li> </li>
</ul> </ul>
{% endlanguage %} {% endlanguage %}

View File

@@ -0,0 +1,30 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Delete Graph Image {% endblock %}
{% block main %}
<ul class="main-content">
<li class="grid_2">
<form action="" method="post">
{% csrf_token %}
<p>Are you sure you want to delete this video analysis?</p>
<p>
<input class="button red" type="submit" value="Confirm">
</p>
</form>
</li>
<li class="grid_2">
<a href="/rowers/video/{{ object.id|encode }}">
<img src="https://img.youtube.com/vi/{{ object.video_id }}/hqdefault.jpg"/>
</a>
</li>
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_workouts.html' %}
{% endblock %}

View File

@@ -35,9 +35,9 @@ $('#id_workouttype').on('change', function(){
|| $(this).val() == 'churchboat' || $(this).val() == 'churchboat'
) { ) {
$('#id_boattype').toggle(true); $('#id_boattype').toggle(true);
} else { } else {
$('#id_boattype').toggle(false); $('#id_boattype').toggle(false);
$('#id_boattype').val('1x'); $('#id_boattype').val('1x');
} }
if ( if (
$(this).val() == 'rower' $(this).val() == 'rower'
@@ -45,13 +45,13 @@ $('#id_workouttype').on('change', function(){
|| $(this).val() == 'slides' || $(this).val() == 'slides'
) { ) {
$('#id_dragfactor').toggle(true); $('#id_dragfactor').toggle(true);
} else { } else {
$('#id_dragfactor').toggle(false); $('#id_dragfactor').toggle(false);
$('#id_dragfactor').val('0'); $('#id_dragfactor').val('0');
} }
}); });
$('#id_workouttype').change(); $('#id_workouttype').change();
}); });
</script> </script>
{% endblock %} {% endblock %}
@@ -114,7 +114,7 @@ $('#id_workouttype').change();
Please correct the error{{ form.errors|pluralize }} below. Please correct the error{{ form.errors|pluralize }} below.
</p> </p>
{% endif %} {% endif %}
<form id="importantform" <form id="importantform"
enctype="multipart/form-data" action="" method="post"> enctype="multipart/form-data" action="" method="post">
@@ -126,9 +126,9 @@ $('#id_workouttype').change();
</form> </form>
</li> </li>
<li class="grid_2"> <li class="grid_2">
<h1>Workout Summary</h1> <h1>Workout Summary</h1>
<p> <p>
<pre> <pre>
{{ workout.summary }} {{ workout.summary }}
@@ -151,13 +151,13 @@ $('#id_workouttype').change();
<script async="true" type="text/javascript"> <script async="true" type="text/javascript">
Bokeh.set_log_level("info"); Bokeh.set_log_level("info");
</script> </script>
<div class="mapdiv"> <div class="mapdiv">
{{ mapdiv|safe }} {{ mapdiv|safe }}
</div> </div>
{{ mapscript|safe }} {{ mapscript|safe }}
</li> </li>
@@ -165,12 +165,19 @@ $('#id_workouttype').change();
{% for graph in graphs %} {% for graph in graphs %}
<li> <li>
<a href="/rowers/graph/{{ graph.id }}/"> <a href="/rowers/graph/{{ graph.id }}/">
<img src="/{{ graph.filename }}" <img src="/{{ graph.filename }}"
onerror="this.src='/static/img/rowingtimer.gif'" onerror="this.src='/static/img/rowingtimer.gif'"
alt="{{ graph.filename }}" width="120" height="100"> alt="{{ graph.filename }}" width="120" height="100">
</a> </a>
</li> </li>
{% endfor %} {% endfor %}
{% for video in videos %}
<li>
<a href="/rowers/video/{{ video.id|encode }}/">
<img src="https://img.youtube.com/vi/{{ video.video_id }}/hqdefault.jpg"/>
</a>
</li>
{% endfor %}
</ul> </ul>
{% endblock %} {% endblock %}

View File

@@ -343,7 +343,11 @@ urlpatterns = [
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/split/$',views.workout_split_view,name='workout_split_view'), re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/split/$',views.workout_split_view,name='workout_split_view'),
# re_path(r'^workout/(?P<id>\d+)/interactiveplot/$',views.workout_biginteractive_view), # re_path(r'^workout/(?P<id>\d+)/interactiveplot/$',views.workout_biginteractive_view),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/view/$',views.workout_view,name='workout_view'), re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/view/$',views.workout_view,name='workout_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/video/$',views.workout_video_view,name='workout_video_view'), re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/video/$',views.workout_video_create_view,
name='workout_video_create_view'),
re_path(r'^video/(?P<pk>\d+)/delete/$',views.VideoDelete.as_view(),name='video_delete'),
re_path(r'^video/(?P<id>\w.+)/$',views.workout_video_view,
name='workout_video_view'),
# re_path(r'^workout/(?P<id>\d+)/$',views.workout_view,name='workout_view'), # re_path(r'^workout/(?P<id>\d+)/$',views.workout_view,name='workout_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/$',views.workout_view,name='workout_view'), re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/$',views.workout_view,name='workout_view'),
re_path(r'^workout/fusion/(?P<id1>\b[0-9A-Fa-f]+\b)/(?P<id2>\b[0-9A-Fa-f]+\b)/$',views.workout_fusion_view,name='workout_fusion_view'), re_path(r'^workout/fusion/(?P<id1>\b[0-9A-Fa-f]+\b)/(?P<id2>\b[0-9A-Fa-f]+\b)/$',views.workout_fusion_view,name='workout_fusion_view'),

View File

@@ -96,7 +96,8 @@ from rowers.models import (
TrainingMesoCycleForm, TrainingMicroCycleForm, TrainingMesoCycleForm, TrainingMicroCycleForm,
RaceLogo,RowerBillingAddressForm,PaidPlan, RaceLogo,RowerBillingAddressForm,PaidPlan,
AlertEditForm, ConditionEditForm, AlertEditForm, ConditionEditForm,
PlannedSessionComment,CoachRequest,CoachOffer,checkaccessplanuser PlannedSessionComment,CoachRequest,CoachOffer,checkaccessplanuser,
VideoAnalysis
) )
from rowers.models import ( from rowers.models import (
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm, RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,

View File

@@ -47,10 +47,125 @@ def get_video_id(url):
raise ValueError raise ValueError
# Show a video compared with data # Show a video compared with data
def workout_video_view(request,id=''):
try:
id = encoder.decode_hex(id)
analysis = VideoAnalysis.objects.get(id=id)
except VideoAnalysis.DoesNotExist:
raise Http404("Video Analysis does not exist")
w = analysis.workout
delay = analysis.delay
if request.user.is_authenticated:
mayedit = checkworkoutuser(request.user,w) and isprorower(request.user.rower)
rower = request.user.rower
else:
mayedit = False
rower = None
# get video ID and offset
if mayedit and request.method == 'POST':
form = VideoAnalysisCreateForm(request.POST)
if form.is_valid():
video_id = form.cleaned_data['url']
delay = form.cleaned_data['delay']
if 'save_button' in request.POST:
analysis.name = form.cleaned_data['name']
analysis.video_id = video_id
analysis.delay = delay
analysis.save()
else:
video_id = id
delay = 0
elif mayedit:
form = VideoAnalysisCreateForm(
initial = {
'name':analysis.name,
'delay': analysis.delay,
'url': analysis.video_id,
}
)
video_id = analysis.video_id
else:
form = None
# get data
df = getsmallrowdata_db(['time','velo','spm'],ids=[w.id],
workstrokesonly=False,doclean=False,compute=False)
df['time'] = (df['time']-df['time'].min())/1000.
df.sort_values(by='time',inplace=True)
df.set_index(pd.to_timedelta(df['time'],unit='s'),inplace=True)
df2 = df.resample('1s').mean().interpolate()
mask = df2['time'] < delay
df2 = df2.mask(mask).dropna()
df2['time'] = (df2['time']-df2['time'].min())
boatspeed = (100*df2['velo']).astype(int)/100.
spm = (10*df2['spm']).astype(int)/10.
coordinates = dataprep.get_latlon_time(w.id)
coordinates.set_index(pd.to_timedelta(coordinates['time'],unit='s'),inplace=True)
coordinates = coordinates.resample('1s').mean().interpolate()
mask = coordinates['time'] < delay
coordinates = coordinates.mask(mask).dropna()
coordinates['time'] = coordinates['time']-coordinates['time'].min()
latitude = coordinates['latitude']
longitude = coordinates['longitude']
# create map
mapscript, mapdiv = leaflet_chart_video(latitude,longitude,
w.name)
# bundle data
data = {
'boatspeed':[ v for v in boatspeed.values],
'latitude':[ l for l in latitude.values],
'longitude':[ l for l in longitude.values],
'spm':[ s for s in spm.values ]
}
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(w.id)),
'name': w.name
},
{
'url':reverse('workout_video_view',kwargs={'id':encoder.encode_hex(analysis.id)}),
'name': 'Video Analysis'
}
]
return render(request,
'embedded_video.html',
{
'workout':w,
'rower':rower,
'data': json.dumps(data,default=default),
'mapscript': mapscript,
'mapdiv': mapdiv,
'video_id': analysis.video_id,
'form':form,
'breadcrumbs':breadcrumbs,
'analysis':analysis,
})
# Create a video compared with data
@user_passes_test(ispromember,login_url="/rowers/paidplans/", @user_passes_test(ispromember,login_url="/rowers/paidplans/",
message="This functionality requires a Pro plan or higher", message="This functionality requires a Pro plan or higher",
redirect_field_name=None) redirect_field_name=None)
def workout_video_view(request,id=0): def workout_video_create_view(request,id=0):
# get workout # get workout
w = get_workout_permitted(request.user,id) w = get_workout_permitted(request.user,id)
@@ -61,6 +176,17 @@ def workout_video_view(request,id=0):
url = form.cleaned_data['url'] url = form.cleaned_data['url']
delay = form.cleaned_data['delay'] delay = form.cleaned_data['delay']
video_id = get_video_id(url) video_id = get_video_id(url)
if 'save_button' in request.POST:
analysis = VideoAnalysis(
workout=w,
name=form.cleaned_data['name'],
video_id = video_id,
delay=delay,
)
analysis.save()
url = reverse('workout_video_view',
kwargs={'id':encoder.encode_hex(analysis.id)})
return HttpResponseRedirect(url)
else: else:
video_id = None video_id = None
delay = 0 delay = 0
@@ -109,6 +235,22 @@ def workout_video_view(request,id=0):
'spm':[ s for s in spm.values ] 'spm':[ s for s in spm.values ]
} }
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(w.id)),
'name': w.name
},
{
'url':reverse('workout_video_create_view',kwargs={'id':encoder.encode_hex(w.id)}),
'name': 'Video Analysis'
}
]
return render(request, return render(request,
'embedded_video.html', 'embedded_video.html',
{ {
@@ -119,6 +261,7 @@ def workout_video_view(request,id=0):
'mapdiv': mapdiv, 'mapdiv': mapdiv,
'video_id': video_id, 'video_id': video_id,
'form':form, 'form':form,
'breadcrumbs':breadcrumbs,
}) })
# Show the EMpower Oarlock generated Stroke Profile # Show the EMpower Oarlock generated Stroke Profile
@@ -3633,6 +3776,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
except: except:
pass pass
videos = VideoAnalysis.objects.filter(workout=row)
# create interactive plot # create interactive plot
f1 = row.csvfilename f1 = row.csvfilename
@@ -3696,6 +3840,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
'workout':row, 'workout':row,
'teams':get_my_teams(request.user), 'teams':get_my_teams(request.user),
'graphs':g, 'graphs':g,
'videos':videos,
'breadcrumbs':breadcrumbs, 'breadcrumbs':breadcrumbs,
'rower':r, 'rower':r,
'indoorraces':indoorraces, 'indoorraces':indoorraces,
@@ -5357,6 +5502,58 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
'formvalues':formvalues, 'formvalues':formvalues,
}) })
class VideoDelete(DeleteView):
login_required = True
model = VideoAnalysis
template_name = 'video_delete_confirm.html'
# extra parameters
def get_context_data(self, **kwargs):
context = super(VideoDelete, self).get_context_data(**kwargs)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(
self.request,
encoder.encode_hex(self.object.workout.id)),
'name': self.object.workout.name
},
{
'url':reverse('workout_video_view',kwargs={'id':encoder.encode_hex(self.object.id)}),
'name': 'Video Analysis'
},
{ 'url':reverse('video_delete',kwargs={'pk':str(self.object.pk)}),
'name': 'Delete'
}
]
context['active'] = 'nav-workouts'
context['rower'] = getrower(self.request.user)
context['breadcrumbs'] = breadcrumbs
return context
def get_success_url(self):
w = self.object.workout
try:
w = Workout.objects.get(id=w.id)
except Workout.DoesNotExist:
return reverse('workouts_view')
return reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)})
def get_object(self, *args, **kwargs):
obj = super(VideoDelete, self).get_object(*args, **kwargs)
if not checkaccessuser(self.request.user,obj.workout.user):
raise PermissionDenied('You are not allowed to delete this analysis')
return obj
class GraphDelete(DeleteView): class GraphDelete(DeleteView):
login_required = True login_required = True