0

I am trying to youtube videos to merge with audio(formate_id:140) and video(formate_id:313) using the youtube-dl library. But it downloads files in the local system. I want it directly download to the client side. I don't want to store files in the local system.

Client-side means downloading files directly to a user system through a browser.

Local system means it's my server.

Example

ydl_opts = {
    'format': '313+140',
    'keepvideo':'false',
}
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
    ydl.download(['https://www.youtube.com/watch?v=Wt8VRxUYDso'])

If any way like merging process and response streaming process work at a time.

r = requests.get(videoUrl, stream=True)
response = StreamingHttpResponse(streaming_content=r)
response['Content-Disposition'] = f'attachement; filename="{fileName}"'
return response

Please guide me in solving this coding problem.

  • It probably cannot be streamed directly to the request in segments, so the best you will get by avoiding saving to file is to load it to RAM into a ByteIO or similar. Usually RAM is much more expensive than storage, so I suggest you stay in this approach, making sure to delete the file after the request is done. – Dan Yishai Dec 29 '21 at 22:27
  • The best approach I found to maybe be able to steam the video in segments is to use [progress hooks](https://github.com/ytdl-org/youtube-dl/blob/3e4cedf9e8cd3157df2457df7274d0c842421945/youtube_dl/YoutubeDL.py#L239) where you get `downloaded bytes` buffers that can be sent to the client in pieces. Not sure how your client will be able to piece them together into a single file at the end... – Dan Yishai Dec 29 '21 at 22:33
  • You can maybe use it to create an iterator that returns each `downloaded_bytes` provided by the hook, then pass it to [StreamingHttpResponse.streaming_content](https://docs.djangoproject.com/en/4.0/ref/request-response/#django.http.StreamingHttpResponse.streaming_content) to steam it to the client one piece at a time. Again, not sure how to implement it and if it will work at all, just an idea I came up with – Dan Yishai Dec 29 '21 at 22:38
  • If I remember correctly, this breaks YouTube TOS? – Dekriel Dec 29 '21 at 23:30

1 Answers1

0

After some investigation, I found an option for youtube_dl called progress_hooks that receives a downloaded_bytes that looks like a bytes buffer of some type (did not find exactly in the documentation).

This hook can maybe be used to send each buffer as a chunk in a Django StreamingHttpResponse. The problem is that it expects to receive the data using a generator passed to streaming_content argument.

So we needed a way to turn the callback hook into a generator. This is similar to FTP client downloading process. I used this also with this great answer and came up with something like:

from queue import Queue

from django.http import StreamingHttpResponse
import youtube_dl


def my_view(request):
    q = Queue()
    job_done = object()

    def process_bytes(progress):
        if progress["status"] == "downloading":
            byte_chunk = progress["downloaded_bytes"]
            q.put(byte_chunk)
            q.join()
        elif progress["status"] == "finished":
            q.put(job_done)

    ydl_opts = {
        'format': '313+140',
        'keepvideo': 'false',
        'progress_hooks': [process_bytes],
    }
    with youtube_dl.YoutubeDL(ydl_opts) as ydl:
        ydl.download(['https://www.youtube.com/watch?v=Wt8VRxUYDso'])

    def chunk_consumer():
        while True:
            chunk = q.get(True, None)  # block, no timeout
            if chunk is job_done:
                break
            else:
                yield chunk
                q.task_done()

    return StreamingHttpResponse(streaming_content=chunk_consumer())

Anyway, I'm not exactly sure whether the client (browser or js) will be able to piece the chunks together into a file.

Dan Yishai
  • 726
  • 3
  • 12