VideoAnalysis object urls
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
{% 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 %}
|
||||
|
||||
30
rowers/templates/video_delete_confirm.html
Normal file
30
rowers/templates/video_delete_confirm.html
Normal 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 %}
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user