I have a Django app that lets you upload a video or image, it sends it to an OpenCV function that returns a new image/video with some annotations on it and then shows it in the template.
For images it works just fine, but for video it doesn't.
The video is shown as unavailable both in the view (as HTML5 video) and when following the /media/[path_to_file], but when I'm opening it with VLC (or some other desktop player, locally), the video exists and is working as expected.
Moreover, I tried removing the OpenCV function and left only the upload functionality. When I uploaded a normal video, everything was fine, but when I tried to upload a video that was previously processed with OpenCV, it appears as unavailable again.
My question is: is opencv changing the video properties in any way comparing to the original so that Django doesn't recognise it anymore? What might be the problem? And how can I make it that opencv processed videos are visible in my view?
Also, the processed video has the same name and extension (and type) as the original.
If you have any idea it's more than helpful. Thanks a lot!
Edit (included the code)
views.py
class IndexView(View):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.context = dict()
def get(self, request, *args, **kwargs):
""" Action for GET requests """
upload_form = UploadForm()
self.context['upload_form'] = upload_form
return render(request, 'homepage/home.html', self.context)
def post(self, request, *args, **kwargs):
""" Action for POST requests """
upload_form = UploadForm(request.POST, request.FILES)
if upload_form.is_valid:
try:
uploaded_file = upload_form.save()
file_type = upload_form.cleaned_data['file'].content_type.split('/')[0]
# task = start_annotation_process.delay(uploaded_file.file.name, file_type)
task = add.delay(4, 5)
except ValidationError:
print('FAILED VALIDATION ERROR')
self.context['upload_status'] = 'failed'
return render(request, 'homepage/home.html', self.context)
except ValueError:
print('FAILED VALUE ERROR')
self.context['upload_status'] = 'failed'
return render(request, 'homepage/home.html', self.context)
self.context['upload_form'] = upload_form
self.context['task_id'] = task.task_id
self.context['uploaded_file'] = uploaded_file
self.context['upload_status'] = 'successfull'
self.context['content_type'] = upload_form.cleaned_data['file'].content_type
return render(request, 'homepage/home.html', self.context)
models.py
class UploadModel(models.Model):
""" Model for storing uploaded files """
file = models.FileField(upload_to='uploads/%Y/%m/%d/')
upload_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return 'UploadModel({file}, {date})'.format(file=self.file, date=self.upload_date)
class Meta:
db_table = 'UploadModel'
forms.py
class UploadForm(forms.ModelForm):
file = forms.FileField(widget=forms.ClearableFileInput(attrs={'class': "custom-file-input",
'id':"input-file",
'aria-describedby':"inputGroupFileAddon01"}))
def clean_file(self):
file = self.cleaned_data.get('file')
if file != None:
file = self.cleaned_data['file'] # try to delete
file_type = file.content_type.split('/')[0]
if file_type == settings.FILE_TYPES[0]: # image
self.__validate_size(file, settings.MAX_UPLOAD_IMAGE_SIZE)
elif file_type == settings.FILE_TYPES[1]: # video
self.__validate_size(file, settings.MAX_UPLOAD_VIDEO_SIZE)
else:
print('File type ', file.content_type)
raise forms.ValidationError(
'File type not supported. It has to be an image or a video.')
return file
def __validate_size(self, file, upload_size):
if file.size > int(upload_size):
raise forms.ValidationError('The file size exceeds {upload_size}. '
'Current file size is {file_size}'.format(upload_size=filesizeformat(upload_size),
file_size=filesizeformat(file.size)))
class Meta:
model = UploadModel
exclude = ('upload_date',)
fields = ('file',)
urls.py (homepage)
urlpatterns = [
path('', homepage_views.IndexView.as_view(), name='homepage'),
path('celery-progress', include('celery_progress.urls')), # the endpoint is configurable
path('download/<str:content_type>/<path:path>', homepage_views.download_file, name='download'),
]
urls.py (root app)
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('homepage.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
tasks.py (celery)
#shared_task(bind=True)
def start_annotation_process(self, file_path, file_type):
""" Celery task for starting the annotation process """
print('start task')
media_src_path = path.join(settings.MEDIA_ROOT, file_path)
media_dst_path = path.join(settings.MEDIA_ROOT, 'results', file_path)
media_res_path = path.join(settings.MEDIA_ROOT, 'results', path.dirname(file_path))
if not path.exists(media_res_path):
makedirs(media_res_path)
if file_type == 'image':
start_process_image(media_src_path, media_dst_path)
elif file_type == 'video':
start_process_video(media_src_path, media_dst_path)
move(media_dst_path, media_src_path)
print('finished task')
Opencv function (FastObjectDetector is a class that will predict the annotations and return the processed frame with predict_img() method)
def start_process_video(source_path, dest_path, process_offline=True, rotate=False):
""" Start annotation process for video """
if not process_offline:
cfod = FastObjectDetector(score_threshold=0.5)
vstrm = VideoCameraStream(logger=cfod.logger,
process_func=cfod.predict_img,
info_func=cfod._DEBUG_INFO,
onclick_func=cfod.on_click,
hd=1,
camera=0)
if vstrm.video != None:
video_frame_shape = (vstrm.H, vstrm.W)
cfod.prepare(image_shape=video_frame_shape)
vstrm.play()
vstrm.shutdown()
if cfod.DEBUG:
cfod.show_fr_stats()
cfod.shutdown()
else:
cfod = FastObjectDetector(score_threshold=0.5)
FRAME_W, FRAME_H = 1280, 720
cfod.prepare(image_shape=(FRAME_H, FRAME_W))
video = cv2.VideoCapture(source_path)
fourcc = int(video.get(cv2.CAP_PROP_FOURCC))
fourcc = cv2.VideoWriter_fourcc(*'XVID')
fps = video.get(cv2.CAP_PROP_FPS)
frame_size = (FRAME_W, FRAME_H)
source_path, _ = os.path.splitext(source_path)
new_video = cv2.VideoWriter(dest_path, fourcc, fps, frame_size)
while (video.isOpened()):
ret, frame = video.read()
if ret:
frame = cv2.resize(frame, (FRAME_W, FRAME_H))
if rotate:
(cW, cH) = (FRAME_W // 2, FRAME_H // 2)
m = cv2.getRotationMatrix2D((cW, cH), -90, 1)
frame = cv2.warpAffine(frame, m, (FRAME_W, FRAME_H))
frame = cfod.predict_img(frame)
new_video.write(frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
video.release()
new_video.release()
cv2.destroyAllWindows()
template.html
<video id="result-video" controls style="width:45vw; height:auto; max-height:40vh">
<p> view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video </p>
</video>
<script type="text/javascript">
{% if task_id and upload_status == "successfull" %}
var progressUrl = "{% url 'celery_progress:task_status' task_id %}";
$('#btn-upload').addClass('disabled');
$(function () {
let options = {
onProgress: function(progressBarElement, progressBarMessageElement, progress){
console.log('ON PROCESS');
$('#div-progress').show();
$('#div-results').hide();
},
onSuccess: function(progressBarElement, progressBarMessageElement){
console.log('ON SUCCESS');
var d = new Date();
{% get_content_type content_type as type %}
{% if type == 'image' %}
$('.result-img').attr('src', '{{ uploaded_file.file.url }}?' + d.getTime());
{% elif type == 'video' %}
$('#result-video').html('<source src="{{ uploaded_file.file.url }}" type="{{ content_type }}">');
$('#result-video')[0].load();
{% endif %}
$('#div-progress').hide();
$('#div-results').show();
$('#btn-upload').removeClass('disabled')
}
};
CeleryProgressBar.initProgressBar(progressUrl, options);
});
{% endif %}
</script>
For the template I used celery_progress's callback functions to add the video source after the celery task is finished. The final HTML file will have the correct source included in the video tag. But the video is unavailable following the /media/ url too.
I hope this helps. Thanks.
I solved the problem myself, so if anyone has the same problem as me, check your codecs inside cv2.VideoWriter_fourcc(*'XVID'). I was trying to upload an .mp4 file while the codec was set for .avi.
This is even more embarrassing as the previous line (fourcc = int(video.get(cv2.CAP_PROP_FOURCC))) was getting the codec from the uploaded file and was about to use it, but the next line was overwriting it.
I ended up using AVC1 codec and changed the code to make any uploaded video into a .mp4 file, just to be sure that this problem will not repeat.
I hope this helps someone.
Related
With Flask-Admin and Flask how can I submit a form\view based on ModelView from code?
I'm trying to create a separate view\form that would allow user to add multiple entries with one form. Specifically allowing to upload multiple images with common prefix name and common parameters. I'd like to do it by submitting a single-image upload form from code, because it does some additional processing like resizing images and I'd like to let Flask-Admin handle connecting database entries and files.
Here's the form I'd like to submit from code:
class ImageView(ModelView):
def _list_thumbnail(view, context, model, name):
if not model.path:
return ''
return Markup('<img src="%s">' % url_for('media',
filename=form.thumbgen_filename(model.path)))
column_labels = dict(show_in_header="Show In Header?",
path="Image")
form_create_rules = ("name",
"tags",
rules.Text(
"Use this image as header. If more than one image is selected header image will be random each time the page is loaded."),
"show_in_header",
"path")
form_excluded_columns = ("timestamp")
column_formatters = {
'path': _list_thumbnail
}
thumbnail_size = config("media", "thumbnail_size")
form_extra_fields = {
'path': BroImageUploadField('Image',
base_path=IMAGES_FOLDER,
thumbnail_size=(thumbnail_size, thumbnail_size, True),
endpoint="media",
url_relative_path='media',
relative_path=None)
}
def is_accessible(self):
return current_user.is_authenticated
def inaccessible_callback(self, name, **kwargs):
# redirect to login page if user doesn't have access
return redirect(url_for('login', next=request.url))
And here I'm creating a form and I'm just not sure that .process() is the function to submit it? Is there one at all?
lass MultipleImagesUploadView(BaseView):
#expose("/", methods=["GET", "POST"])
def index(self):
if request.method == "POST":
a_form = MultipleImagesForm(request.form)
base_name = a_form.base_name.data
tags = a_form.tags.data
show_in_header = a_form.show_in_header.data
print (request.files.getlist(a_form.images.name))
uploaded_files = request.files.getlist(a_form.images.name)
for i, uf in enumerate(uploaded_files):
try:
name, ext = os.path.splitext(uf.filename)
if ext not in IMAGE_EXTENSIONS:
flash("Image file {} was skipped as it's extension is not supported ({}).".format(uf.filename, ext), category="warning")
continue
image_contents = uf.stream.read()
image_form = ImageView()
image_form.name.data = "{}_{}".format(base_name, i)
image_form.tags.data = tags
image_form.show_in_header.data = show_in_header
image_form.path.data = image_contents
image_form.process()
except Exception as e:
flash ("Unhandled exception: {}".format(e), category="warning")
flash("Images were added to the gallery.", category='success')
a_form = MultipleImagesForm()
print("############", a_form)
return self.render('/admin/multiple_images_upload.html', form=a_form)
I can't figure out a way to submit a form from code, been trying to find the answer in docs and google for hours now with no luck.
Found the issue. In my case I was missing the enctype="multipart/form-data". Without that files part was sent as empty.
Also changed to using from flask_wtf import FlaskForm and enabling it as {{ form.files(class="form-control", multiple="") }} in the template.
Files can then be accessed with uploaded_files = request.files.getlist("files") on POST request, it will hold array of file-objects.
I hope this helps someone. If any additional formatting is required I will add or expand the answer.
I'm using django 2.0 and python 3.6, I've tried almost everything to get the values from request.FILES but nothing seems to work, I think that I'm missing something very simple but I can't find it.
models.py
class Imagen(models.Model):
imagen = models.FileField(max_length=200, blank=True, upload_to='%Y/%m/%d/')
views.py
def prop_add(request):
if request.method == 'POST':
propiedad_instance = Propiedad(tipo = request.POST.get('tipo'), tamano=request.POST.get('tamano'), habitaciones = request.POST.get('habitaciones'), banos = request.POST.get('banos'), descripcion= request.POST.get('descripcion'), direccion= request.POST.GET('descripcion'), precio= request.POST.get('precio'), ubicacion= Ubicacion.objects.get(barrio= request.POST.get('barrio')))
propiedad_intance.save()
for filename in request.FILES.iteritems():
name = request.FILES[filename].name
print('name =' + name) ## <-- not printing anything
print('file = ' + file)## <-- not printing anything
print('filename = ' + filename)## <-- not printing anything
ubicaciones = Ubicacion.objects.all()
ctx = {'ubicaciones': ubicaciones}
return render(request, 'main/add_modal.html', ctx)
HTML template
<form method='post' action='' enctype="multipart/form-data">
<input name='imagen' type="file" multiple/>
<button type='submit' class="btn waves-effect">Upload</button>
</form>
that views.py is the one that I have right now but so far I've tried all the following variants:
**1**
if request.method == 'POST':
for f in request.FILES.getlist('imagen'):
filename = f.name
print(filename) ## <-- not printing anything
**2**
if request.method== 'POST':
form = FileUploadForm(request.POST, request.FILES) ## form imported from forms.py
if form.is_valid():
print('form is valid!') ## <-- not printing anything
else:
print('form not valid ') ## <-- not printing anything either!! IDK WHY
**3**
if request.method == 'POST':
print('request.method = "POST" checked ') # <-- Not priting anything! but the model below is being saved to the database! my brain is about to explode right now haha.
propiedad_instance = Propiedad(tipo = request.POST.get('tipo'), tamano=request.POST.get('tamano'), habitaciones = request.POST.get('habitaciones'), banos = request.POST.get('banos'), descripcion= request.POST.get('descripcion'), direccion= request.POST.GET('descripcion'), precio= request.POST.get('precio'), ubicacion= Ubicacion.objects.get(barrio= request.POST.get('barrio')))
propiedad_intance.save()
files = request.FILES.getlist('imagen')
if files:
for f in files:
print('something') #<-- not printing anything
print(f) #<-- not printing anything
print(f.name) <-- not printing anything
else:
print('nothing here') #<--- not printing anything
Console log after submitting the form
enter image description here
The correct way to upload multiple files as per the documentation would be..
files = request.FILES.getlist('file_field') #'file_field' --> 'imagen' for you
if form.is_valid():
for f in files:
... # Do something with each file.
Your html looks ok. Replace your view code as per the above and check if its making to the POST in the view?
Django Docs - Multiple File Upload
I am loading some images into a Django model.
When I display these images through a Django template, portrait images are rotated. If I view these same images through Django Admin by clicking on the link they display as I would expect.
This is my view to load the images:
def upload_profile(request, user_id):
variables = {}
if request.POST:
user_form = UserForm(request.POST, instance=User.objects.get(id=request.user.id))
myuser_form = MyUserForm(request.POST, request.FILES, instance=MyUser.objects.get(user__id=request.user.id))
if user_form.is_valid() and myuser_form.is_valid():
user_form.save()
myuser = myuser_form.save()
myuser.photo = apply_orientation(myuser.photo)
myuser.save()
return game_selection(request)
variables['user_form'] = user_form
variables['myuser_form'] = myuser_form
return render(request, 'esc/profile.html', variables)
And this is how I am applying the rotation:
def flip_horizontal(im): return im.transpose(Image.FLIP_LEFT_RIGHT)
def flip_vertical(im): return im.transpose(Image.FLIP_TOP_BOTTOM)
def rotate_180(im): return im.transpose(Image.ROTATE_180)
def rotate_90(im): return im.transpose(Image.ROTATE_90)
def rotate_270(im): return im.transpose(Image.ROTATE_270)
def transpose(im): return rotate_90(flip_horizontal(im))
def transverse(im): return rotate_90(flip_vertical(im))
orientation_funcs = [None,
lambda x: x,
flip_horizontal,
rotate_180,
flip_vertical,
transpose,
rotate_270,
transverse,
rotate_90
]
def apply_orientation(im):
"""
Extract the oritentation EXIF tag from the image, which should be a PIL Image instance,
and if there is an orientation tag that would rotate the image, apply that rotation to
the Image instance given to do an in-place rotation.
:param Image im: Image instance to inspect
:return: A possibly transposed image instance
"""
try:
kOrientationEXIFTag = 0x0112
if hasattr(im, '_getexif'): # only present in JPEGs
e = im._getexif() # returns None if no EXIF data
if e is not None:
#log.info('EXIF data found: %r', e)
orientation = e[kOrientationEXIFTag]
f = orientation_funcs[orientation]
return f(im)
except:
# We'd be here with an invalid orientation value or some random error?
pass # log.exception("Error applying EXIF Orientation tag")
return im
My code never seems to pass the condition if hasattr(im, '_getexif'):
**** EDIT ****
So it's not getting through because no matter what image I load there is no '_getexif'.
If I add print getattr(im, '_getexif') I get the following error, with all images
'ImageFieldFile' object has no attribute '_getexif'
So I dropped all the code above and replaced it with the following
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill, Transpose, SmartResize
class MyUser(models.Model):
avatar = models.ImageField(upload_to='upload/avatars', max_length=255, blank=True, null=True)
avatar_thumbnail = ImageSpecField(
source='avatar',
processors = [Transpose(),SmartResize(200, 200)],
format = 'JPEG',
options = {'quality': 75}
)
It works
I'm trying to implement an ajax upload in my DJANGO app using (django-ajax-uploader). everything seems to work fine but when i upload a file i get upload failed with a 500 error caused by a bad response from AWS S3:
S3ResponseError at /ajax-upload↵S3ResponseError: 400 Bad Request↵<Error><Code>MalformedXML</Code><Message>The XML you provided was not well-formed or did not validate...
here is my backend class :
from ajaxuploader.backends.base import AbstractUploadBackend
class S3Upload(AbstractUploadBackend):
NUM_PARALLEL_PROCESSES = 4
def upload_chunk(self, chunk, *args, **kwargs):
self._counter += 1
buffer = StringIO()
buffer.write(chunk)
self._pool.apply_async(
self._mp.upload_part_from_file(buffer, self._counter))
buffer.close()
def setup(self, filename, *args, **kwargs):
self._bucket = boto.connect_s3(
settings.AWS_ACCESS_KEY_ID,
settings.AWS_SECRET_ACCESS_KEY
).lookup(settings.AWS_BUCKET_NAME)
self._mp = self._bucket.initiate_multipart_upload(filename)
self._pool = Pool(processes=self.NUM_PARALLEL_PROCESSES)
self._counter = 0
def upload_complete(self, request, filename, *args, **kwargs):
# Tie up loose ends, and finish the upload
self._pool.close()
self._pool.join()
self._mp.complete_upload()
Template (javascript):
<script src="{% static "ajaxuploader/js/fileuploader.js" %}"></script>
<script>
$(function(){
var uploader = new qq.FileUploader({
action: "{% url "my_ajax_upload" %}",
element: $('#file-uploader')[0],
multiple: true,
onComplete: function(id, fileName, responseJSON) {
if(responseJSON.success) {
alert("success!");
} else {
alert("upload failed!");
}
},
onAllComplete: function(uploads) {
// uploads is an array of maps
// the maps look like this: {file: FileObject, response: JSONServerResponse}
alert("All complete!");
},
params: {
'csrf_token': '{{ csrf_token }}',
'csrf_name': 'csrfmiddlewaretoken',
'csrf_xname': 'X-CSRFToken',
},
});
});
</script>
I solved this problem with a custom s3 backend that override the upload function & use django-storages instead of boto to save files. try this :
from ajaxuploader.backends.base import AbstractUploadBackend
from django.core.files.storage import default_storage
class S3CustomUpload(AbstractUploadBackend):
NUM_PARALLEL_PROCESSES = 4
def upload_chunk(self, chunk):
#save file to s3
self._fd.write(chunk)
self._fd.close()
def setup(self, filename):
self._fd = default_storage.open('%s/%s' % ('uploads/materials/', str(filename)), 'wb')
def upload(self, uploaded, filename, raw_data, *args, **kwargs):
try:
if raw_data:
# File was uploaded via ajax, and is streaming in.
chunk = uploaded.read(self.BUFFER_SIZE)
while len(chunk) > 0:
self.upload_chunk(chunk, *args, **kwargs)
chunk = uploaded.read(self.BUFFER_SIZE)
else:
# File was uploaded via a POST, and is here.
for chunk in uploaded.chunks():
self.upload_chunk(chunk, *args, **kwargs)
return True
except:
# things went badly.
return False
def upload_complete(self, request, filename, *args, **kwargs):
upload = Upload()
upload.upload = settings.S3_URL + "uploads/materials/"+ filename
upload.name = filename
upload.save()
return {'pk': upload.pk}
This works for me:
def upload_chunk(self, chunk, *args, **kwargs):
self._counter += 1
buffer = BytesIO(chunk)
self._pool.apply_async(
self._mp.upload_part_from_file(buffer, self._counter))
buffer.close()
I am having a hard time with tests in Django and Python, for my final project I am making a forums website, but I don't really have any idea how or what my tests should be. Here is the views page from mysite file. Could someone please walk me through what I should test for besides if a user is logged in.
from django.core.urlresolvers import reverse
from settings import MEDIA_ROOT, MEDIA_URL
from django.shortcuts import redirect, render_to_response
from django.template import loader, Context, RequestContext
from mysite2.forum.models import *
def list_forums(request):
"""Main listing."""
forums = Forum.objects.all()
return render_to_response("forum/list_forums.html", {"forums":forums}, context_instance=RequestContext(request))
def mk_paginator(request, items, num_items):
"""Create and return a paginator."""
paginator = Paginator(items, num_items)
try: page = int(request.GET.get("page", '1'))
except ValueError: page = 1
try:
items = paginator.page(page)
except (InvalidPage, EmptyPage):
items = paginator.page(paginator.num_pages)
return items
def list_threads(request, forum_slug):
"""Listing of threads in a forum."""
threads = Thread.objects.filter(forum__slug=forum_slug).order_by("-created")
threads = mk_paginator(request, threads, 20)
template_data = {'threads': threads}
return render_to_response("forum/list_threads.html", template_data, context_instance=RequestContext(request))
def list_posts(request, forum_slug, thread_slug):
"""Listing of posts in a thread."""
posts = Post.objects.filter(thread__slug=thread_slug, thread__forum__slug=forum_slug).order_by("created")
posts = mk_paginator(request, posts, 15)
thread = Thread.objects.get(slug=thread_slug)
template_data = {'posts': posts, 'thread' : thread}
return render_to_response("forum/list_posts.html", template_data, context_instance=RequestContext(request))
def post(request, ptype, pk):
"""Display a post form."""
action = reverse("mysite2.forum.views.%s" % ptype, args=[pk])
if ptype == "new_thread":
title = "Start New Topic"
subject = ''
elif ptype == "reply":
title = "Reply"
subject = "Re: " + Thread.objects.get(pk=pk).title
template_data = {'action': action, 'title' : title, 'subject' : subject}
return render_to_response("forum/post.html", template_data, context_instance=RequestContext(request))
def new_thread(request, pk):
"""Start a new thread."""
p = request.POST
if p["subject"] and p["body"]:
forum = Forum.objects.get(pk=pk)
thread = Thread.objects.create(forum=forum, title=p["subject"], creator=request.user)
Post.objects.create(thread=thread, title=p["subject"], body=p["body"], creator=request.user)
return HttpResponseRedirect(reverse("dbe.forum.views.forum", args=[pk]))
def reply(request, pk):
"""Reply to a thread."""
p = request.POST
if p["body"]:
thread = Thread.objects.get(pk=pk)
post = Post.objects.create(thread=thread, title=p["subject"], body=p["body"],
creator=request.user)
return HttpResponseRedirect(reverse("dbe.forum.views.thread", args=[pk]) + "?page=last")
First read the Django testing documentation. You might also want to read this book. It's dated in some areas, but testing is still pretty much the same now as it was in 1.1.
It's a bit much of a topic to cover in an SO answer.
Well, you could test:
If you have the right number of pages for the objects you're paginating.
If the page you're viewing contains the right object range. If trying to access a page that doesn't exist returns the
appropriate error.
If your views for listing objects and object detail return the correct HTTP status code (200)
For starters. Hope that helps you out.