pythonpython-3.xpyqt5urllibqmediaplayer

How QMediaContent able to show a video when urllib unable to download?


I have a Qt application to show videos from urls using:

player = QMediaPlayer()
...
player.setMedia(QMediaContent(QUrl(video.url)))
...

But unable to download the video using the same url with urllib.request, response code is always 200 but Content-Length is zero.

from urllib.request import urlopen, Request
rq = Request(video.url)
rp = urlopen(rq)
rp.headers["Content-Length"] # always 0

How Qt is able to show video when im failing to download?

MWE

from PyQt5.QtWidgets import QApplication
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from PyQt5.QtCore import QUrl
from urllib.request import urlopen
import sys

class Test(QVideoWidget):
    def __init__(self, *args, **kwargs):
        QVideoWidget.__init__(self, *args, **kwargs)

        self.player = QMediaPlayer()
        self.player.setVideoOutput(self)
        self.player.mediaStatusChanged.connect(self.statusChanged)

        self.url = "https://api16-normal-c-useast1a.tiktokv.com/aweme/v1/play/?video_id=v09044190000brfkq160bkbi3ui1oko0&line=0&ratio=540p&media_type=4&vr_type=0&improve_bitrate=0&logo_name=tiktok_m&quality_type=11&source=PackSourceEnum_AWEME_DETAIL"
        self.player.setMedia(QMediaContent(QUrl(self.url)))


    def statusChanged(self, status):
        if status == QMediaPlayer.LoadedMedia:
            self.player.play()
        elif status == QMediaPlayer.EndOfMedia:
            self.player.play()

    
    def download(self):
        rp = urlopen(self.url)
        if int(rp.headers["Content-Length"]) != 0:
            with open("test.mp4", "wb") as mp4:
                while True:
                    chunk = rp.read(1024)
                    if not chunk: break
                    mp4.write(chunk)
        else:
            raise Exception("Content-Length is Zero")



if __name__ == "__main__":
    app = QApplication(sys.argv)
    test = Test()
    test.show()
    # uncomment to download
    # test.download()
    sys.exit(app.exec_())

Solution

  • After several attempts I found that you must set the "user-agent". Qt does not directly handle QMediaPlayer requests but backends like gstreamer on Linux, and these set the user-agent so it works correctly.

    I also found that you shouldn't be the user-agent of browsers like "Mozilla/5.0 ..." are probably rejected as a means of protection.

    from urllib.request import Request, urlopen
    
    # ...
    
    class Test(QVideoWidget):
        # ...
        def download(self):
            rq = Request(self.url, headers={"user-agent": "MyApp/1.0"})
            with urlopen(rq) as rp:
                with open("test.mp4", "wb") as mp4:
                    while True:
                        chunk = rp.read(1024)
                        if not chunk:
                            break
                        mp4.write(chunk)