Django PIPE youtube-dl to view for download - django

TL;DR: I want to pipe the output of youtube-dl to the user's browser on a button click, without having to save the video on my server's disk.
So I'm trying to have a "download" button on a page (django backend) where the user is able to download the video they're watching.
I am using the latest version of youtube-dl.
In my download view I have this piece of code:
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
file = ydl.download([f"https://clips.twitch.tv/{pk}"])
And it works, to some extend. It does download the file to my machine, but I am not sure how to allow users to download the file.
I thought of a few ways to achieve this, but the only one that really works for me would be a way to pipe the download to user(client) without needing to store any video on my disk. I found this issue on the same matter, but I am not sure how to make it work. I successfully piped the download to stdout using ydl_opts = {'outtmpl': '-'}, but I'm not sure how to pipe that to my view's response. One of the responses from a maintainer mentions a subprocess.Popen, I looked it up but couldn't make out how it should be implemented in my case.

I did a workaround.
I download the file with a specific name, I return the view with HttpResponse, with force-download content-type, and then delete the file using python.
It's not what I originally had in mind, but it's the second best solution that I could come up with. I will select this answer as accepted solution until a Python wizard gives a solution to the original question.
The code that I have right now:
def download_clip(request, pk):
ydl_opts = {
'outtmpl': f"{pk}.mp4"
}
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
ydl.download([f"https://clips.twitch.tv/{pk}"])
path = f"{pk}.mp4"
file_path = os.path.join(path)
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(fh.read(), content_type="application/force-download")
response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
os.remove(file_path)
return response
raise Http404

Related

Stop spinner after Django FileResponse Save

I have a template link of a url that runs a view function that generates a file and returns a FileResponse. Works great, but in cases it can take a while to generate the file so I'd like to start and stop a spinner before and after.
I've tried using a click event on the link to run and $.ajax() or $.get() function that sends the url, and in this way I can start the spinner. But the FileResponse doesn't generate a Save window in this case. (code below)
Is there a way to generate and save a file in a Django view via JavaScript? The following never opens the file save window.
$("#ds_downloads a").click(function(e){
urly='/datasets/{{ds.id}}/augmented/'+$(this).attr('ref')
startDownloadSpinner()
$.ajax({
type: 'GET',
url: urly
}).done(function() {
spinner_dl.stop();
})
})
Adding the function below that creates the FileResponse. This generates a local system file save window allowing the user to save the file locally. The event of either opening or closing (save) that window would be the time to stop the spinner, but I can't seem to access it via javascript.
with open(fn, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile, delimiter='\t',
quotechar='', quoting=csv.QUOTE_NONE)
writer.writerow(['col1','col2','col3'])
for f in features:
geoms = f.geoms.all()
gobj = augGeom(geoms)
row = [
gobj['col1'],
gobj['col2'],
gobj['col3']]
writer.writerow(row)
response = FileResponse(open(fn, 'rb'),content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="'+os.path.basename(fn)+'"'
Just use HttpResponse, the browser adds the spinner automatically. No need to add extra spinner.
from csv import writer
from django.http import HttpResponse
response = HttpResponse(content_type='text/csv')
response.status_code = 200
response["Content-Disposition"] = "attachment; filename={}".format(filename)
try:
csv_writer = writer(response)
# define headers
csv_writer.writerow(headers)
[csv_writer.writerow(row) for row in features.values_list(*headers)]
except Exception:
raise
return response
I came up with a somewhat klugey solution: I generate a POST request with $.ajax to a function-based view that initiates a Celery task to perform the file creation and save it to the filesystem. It returns the Celery task_id to the browser while the task runs, feeding that to a celery_progress.js routine that checks the progress of the task and on completion returns the filename; an href link is created with that in the markup, and a click() generated on it. That ajax function starts and stops a spinner with spinner.js at the appropriate points.
Not posting code b/c it is a kluge and I wouldn't recommend it. I'm guessing there's a better way to do this with a django form, but this works, the project is overdue, and I haven't got the time to look further.

Django - Custom header to application/x-zip-compressed HttpResponse

I have a Django App which contains a view that returns a .zip file using HttpResponse
resp = HttpResponse(s.getvalue(), content_type="application/x-zip-compressed")
resp['Content-Disposition'] = 'attachment; filename=%s' % zip_filename
return resp
The .zip is created inside the view where I also calculate its checksum.
On the client-side I use a requests.get() to get the zip file.
How can I send the checksum in the same HttpResponse along with the zip.
I tried adding the checksum to the header by
resp['hash-key'] = sha_checksum
but on the client requests.headers['hash-key'] seems to be None
How can I do this?
Edit:
As it seems my problem is located in the calculation of the hash which results in None.
The strange thing is that the same function is used on the client-side and works fine, but I guess this is another question.
Since hash-key is in the response, it sounds like this line is working.
resp['hash-key'] = sha_checksum
Try printing the value of sha_checksum before you assign it, to make sure it is not None.

Django - writing a PDF created with xhtml2pdf to server's disk

I'm trying to write a script that will save a pdf created by xhtml2pdf directly to the server, without doing the usual route of prompting the user to download it to their computer. Documents() is the Model I am trying to save to, and the new_project and output_filename variables are set elsewhere.
html = render_to_string(template, RequestContext(request, context)).encode('utf8')
result = open(output_filename, "wb")
pdf = CreatePDF(src=html, dest=results, path = "", encoding = 'UTF-8', link_callback=link_callback) #link callback was originally set to link_callback, defined below
result.close()
if not pdf.err:
new_doc=Documents()
new_doc.project=new_project
new_doc.posted_by=old_mess[0].from_user_fk.username
new_doc.documents = result
new_doc.save()
With this configuration when it reaches new_doc.save() I get the error: 'file' object has no attribute '_committed'
Does anyone know how I can fix this? Thanks!
After playing around with it I found a working solution. The issue was I was not creating the new Document while result (the pdf) was still open.
"+" needed to be added to open() so that the pdf file was available for reading and writing, and not just writing.
Note that this does save the pdf in a different folder first (Files). If that is not the desired outcome for your application you will need to delete it.
html = render_to_string(template, RequestContext(request, context)).encode('utf8')
results = StringIO()
result = open("Files/"+output_filename, "w+b")
pdf = CreatePDF(src=html, dest=results, path = "", encoding = 'UTF-8', link_callback=link_callback) #link callback was originally set to link_callback, defined below
if not pdf.err:
result.write(results.getvalue())
new_doc=Documents()
new_doc.project=new_project
new_doc.documents.save(output_filename, File(result))
new_doc.posted_by=old_mess[0].from_user_fk.username
new_doc.save()
result.close()

How to process an uploaded KML file in GeoDjango

I wrote a cmd line routine to import a kml file into a geoDjango application, which works fine when you feed it a locally saved KML file path (using the datasource object).
Now I am writing a web file upload dialog, to achieve the same thing. This is the beginning of the code that I have, problem is, that the GDAL DataSource object does not seem to understand Djangos UploadedFile format. It is held in memory and not a file path as expected.
What would be the best strategy to convert the UploadedFile to a normal file, and access this through a path? I dont want to keep the file after processing.
def createFeatureSet(request):
if request.method == 'POST':
inMemoryFile = request.FILES['myfile']
name = inMemoryFile.name
POSTGIS_SRID = 900913
ds = DataSource(inMemoryFile) #This line doesnt work!!!
for layer in ds:
if layer.geom_type in (OGRGeomType('Point'), OGRGeomType('Point25D'), OGRGeomType('MultiPoint'), OGRGeomType('MultiPoint25D')):
layerGeomType = OGRGeomType('MultiPoint').django
elif layer.geom_type in (OGRGeomType('LineString'),OGRGeomType('LineString25D'), OGRGeomType('MultiLineString'), OGRGeomType('MultiLineString25D')):
layerGeomType = OGRGeomType('MultiLineString').django
elif layer.geom_type in (OGRGeomType('Polygon'), OGRGeomType('Polygon25D'), OGRGeomType('MultiPolygon'), OGRGeomType('MultiPolygon25D')):
layerGeomType = OGRGeomType('MultiPolygon').django
DataSource is a wrapper around GDAL's C API and needs an actual file. You'll need to write your upload somewhere on the disk, for insance using a tempfile. Then you can pass the file to DataSource.
Here is a suggested solution using a tempfile. I put the processing code in its own function which is now called.
f = request.FILES['myfile']
temp = tempfile.NamedTemporaryFile(delete=False)
temp.write(f.read())
temp.close()
createFeatureSet(temp.name, source_SRID= 900913)

Serving a dynamically generated MS Excel files using django and xlwt fails in Internet Explorer

I am trying to use xlwt to create MS-Excel files from the contents of the database on my django site.
I have seen several solutions here on stackoverflow, in particular this link: django excel xlwt
and this django snippet: http://djangosnippets.org/snippets/2233/
These examples work in firefox, but not in Internet Explorer. Instead of getting prompted to open or save a file, a bunch of wingding junk appears on the screen. It seems that IE thinks the response is html.
Here is my view function:
def exportexcel(request):
from xlwt import Workbook
wb = Workbook()
ws = wb.add_sheet('Sheetname')
ws.write(0, 0, 'Firstname')
ws.write(0, 1, 'Surname')
ws.write(1, 0, 'Hans')
ws.write(1, 1, 'Muster')
fname = 'testfile.xls'
response = HttpResponse(mimetype="application/ms-excel")
response['Content-Disposition'] = 'attachment; filename=%s' % fname
wb.save(response)
return response
I am seeing this behavior in IE 8.
Any suggestions as to why this isn't working in Internet Explorer?
Thanks.
The mimetype you're using application/ms-excel is invalid for .xls files.
The standard one is application/vnd.ms-excel
Look here Setting mime type for excel document for more informations.