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)
link = models.TextField(max_length=300)
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 %}
<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' %}
<h1>Workout Video for {{ workout.name }}</h1>
<h1>Video Analysis for {{ workout.name }}</h1>
<ul class="main-content">
<li class="grid_4">
<p>
This page will contain an embedded video
</p>
</li>
<li class="grid_4">
<div style="height:100%" id="theplot" class="flexplot mapdiv">
{{ mapdiv | safe}}
@@ -126,21 +111,36 @@
</script>
</li>
<li class="grid_4">
{% if form %}
{% if not video_id %}
<p>
To load your video, paste the URL of your YouTube video in the form below,
and submit the form.
</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 %}
<p>
<form enctype="multipart/form-data" action="" method="post">
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<input type="submit" value="submit">
</form>
<form enctype="multipart/form-data" action="" method="post">
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<input type="submit" name="reload_button" value="Reload">
<input type="submit" name="save_button" value="Save">
</form>
</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>
</ul>
{% 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'
) {
$('#id_boattype').toggle(true);
} else {
} else {
$('#id_boattype').toggle(false);
$('#id_boattype').val('1x');
$('#id_boattype').val('1x');
}
if (
$(this).val() == 'rower'
@@ -45,13 +45,13 @@ $('#id_workouttype').on('change', function(){
|| $(this).val() == 'slides'
) {
$('#id_dragfactor').toggle(true);
} else {
} else {
$('#id_dragfactor').toggle(false);
$('#id_dragfactor').val('0');
$('#id_dragfactor').val('0');
}
});
$('#id_workouttype').change();
$('#id_workouttype').change();
});
</script>
{% endblock %}
@@ -114,7 +114,7 @@ $('#id_workouttype').change();
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<form id="importantform"
enctype="multipart/form-data" action="" method="post">
@@ -126,9 +126,9 @@ $('#id_workouttype').change();
</form>
</li>
<li class="grid_2">
<h1>Workout Summary</h1>
<p>
<pre>
{{ workout.summary }}
@@ -151,13 +151,13 @@ $('#id_workouttype').change();
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
<div class="mapdiv">
{{ mapdiv|safe }}
</div>
{{ mapscript|safe }}
</li>
@@ -165,12 +165,19 @@ $('#id_workouttype').change();
{% for graph in graphs %}
<li>
<a href="/rowers/graph/{{ graph.id }}/">
<img src="/{{ graph.filename }}"
<img src="/{{ graph.filename }}"
onerror="this.src='/static/img/rowingtimer.gif'"
alt="{{ graph.filename }}" width="120" height="100">
</a>
</li>
{% 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>
{% 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>\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)/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>\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'),

View File

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

View File

@@ -47,10 +47,125 @@ def get_video_id(url):
raise ValueError
# 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/",
message="This functionality requires a Pro plan or higher",
redirect_field_name=None)
def workout_video_view(request,id=0):
def workout_video_create_view(request,id=0):
# get workout
w = get_workout_permitted(request.user,id)
@@ -61,6 +176,17 @@ def workout_video_view(request,id=0):
url = form.cleaned_data['url']
delay = form.cleaned_data['delay']
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:
video_id = None
delay = 0
@@ -109,6 +235,22 @@ def workout_video_view(request,id=0):
'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,
'embedded_video.html',
{
@@ -119,6 +261,7 @@ def workout_video_view(request,id=0):
'mapdiv': mapdiv,
'video_id': video_id,
'form':form,
'breadcrumbs':breadcrumbs,
})
# Show the EMpower Oarlock generated Stroke Profile
@@ -3633,6 +3776,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
except:
pass
videos = VideoAnalysis.objects.filter(workout=row)
# create interactive plot
f1 = row.csvfilename
@@ -3696,6 +3840,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
'workout':row,
'teams':get_my_teams(request.user),
'graphs':g,
'videos':videos,
'breadcrumbs':breadcrumbs,
'rower':r,
'indoorraces':indoorraces,
@@ -5357,6 +5502,58 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
'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):
login_required = True