Webserver und Qt im Zusammenspiel

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Hallo Community,

ich entwickle grade eine kleine Anwendung bei der ich Qt Klassen, im speziellen QWebPage von einem Webframework (bottle) fernsteuern will.
Im konkreten will ich Youtube Videos abspielen/pausieren/stoppen können und dies alles über eine Webseite. Dies wird über die Youtube Player API (javascript) gehandhabt.

Ich hab mir das ganze so gedacht, das ich jeweils das Webframework und die QWebPage in einen separaten Thread packe, à la:

Code: Alles auswählen

class WebServer(QtCore.QThread):

    def run(self):
        debug()
        run(host='localhost', port=8080, reloader=True)

class YouTubePlayer(QtCore.QThread):

    def run(self):
        webview = QtWebKit.QWebView()
        # usw.

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    webserver = WebServer()
    webserver.start()
    youtubeplayer = YouTubePlayer()
    youtubeplayer.start()
    sys.exit(app.exec_())
Der Webserver wird zwar gestartet, aber der Qt Teil zeigt keine Wirkung. Meine Vermutung ist, dass QWidgets außerhalb des Haupthreads verboten sind und es deswegen nicht klappt, oder liege ich da falsch?

Habt ihr einen anderen Ansatz bzw. vielleicht einen GANZ anderen Ansatz? Später will ich, dass beide Threads via Signale miteinander kommunizieren. Das Ganze in Prozesse auszulagern, fände ich nicht so schön, dann müsste ich wahrscheinlich mit IPC bzw. TCP/IP rumhantieren.

Grüße,
anogayales
lunar

@anogayales: Ich habe nicht alles verstanden, insbesondere nicht, warum Du in "WebServer" offenbar einen eigenen Webserver startest...

In jedem Fall gehst Du richtig in der Annahme, dass Du Widgets nicht außerhalb des Hauptthreads erzeugen darfst. Anders gesagt, jedwede Interaktion mit der graphischen Oberfläche muss im Hauptthread erfolgen, alles andere ist undefiniertes Verhalten.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Ich will einen Webserver bauen, der wenn er z.B. die URL: /XZ5TajZYW6Y bekommt das Video http://www.youtube.com/watch?v=XZ5TajZYW6Y abspielt. Weiter soll der Webserver die Möglichkeit haben, das Lied kurz zu pausieren, z.B. bei der URL /pause.

Um die Youtube Player API benutzen zu können, ist es leider von Nöten, den Player auf einen Server auszulagen, siehe http://code.google.com/intl/de-DE/apis/ ... st_locally Ich benutze also den Server in zweierlei hinsicht, einmal um ihn zu hosten und einmal um mit ihm zu interagieren.

Hoffe ich konnte mein Problem näher erläutern. Was ich nun wissen will, ist wie ich das ganze so aufbauen kann, dass der Server sowohl Nachrichten vom Player bekommt und wie der Server Nachrichten zum Player schickt. Die einzelnen Komponenten funktionieren bereits, z.B. eine Abfrage wie Lange das Video schon läuft bekomme ich über:

Code: Alles auswählen

webview.page().currentFrame().evaluateJavaScript("ytplayer.getCurrentTime()")
Grüße,
anogayales
Zuletzt geändert von anogayales am Samstag 7. Januar 2012, 12:35, insgesamt 1-mal geändert.
lunar

@anogayales: Warum der Umweg über den Youtube-Player?! Du kannst Youtube-Videos doch einfach direkt abspielen, indem Du das Video einfach über Phonon streamst.

In jedem Fall gibt es keinen direkten Weg, über Javascript aus einer Website heraus ein Signal an eine Desktop-Anwendung zu senden. Am einfachsten ist es wohl, ein eigenes Objekt in den Namensraum der Website einzufügen (siehe "QWebFrame.addToJavaScriptWindowObject") und dann mit eigenem Javascript-Code in der Seite auf dieses Objekt zuzugreifen.
deets

@anogayles

Ich glaube zwar, dass du da ziemlich konfus in deiner Benutzung des Wortes "Webserver" bist, aber sei's drum...

Was du erreichen willst, kannst du durch das einbetten einen QObject in das Document, das der WebView gerade zeigt. Ich finde den Code dafuer gerade nicht, aber das ist ziemlich simpel. Dann ist das Objekt unter einem globalen Namen in JS verfuegbar, und du kannst darauf zugreifen. Und du solltest auch Signale emittieren koennen.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

deets hat geschrieben:Ich glaube zwar, dass du da ziemlich konfus in deiner Benutzung des Wortes "Webserver" bist, aber sei's drum...
Hab ich mir fast schon gedacht
deets hat geschrieben:Und du solltest auch Signale emittieren koennen.
Das Problem ist aber immernoch nicht gelöst wie ich von bottle auf dieses QObject zugreife. AFAIK hat bottle und Qt seine eigene "Event Loop" und mit Threads gehts ja nicht.
lunar hat geschrieben: Warum der Umweg über den Youtube-Player?! Du kannst Youtube-Videos doch einfach direkt abspielen, indem Du das Video einfach über Phonon streamst.
Wie kommt man an die Steamdaten ran? Ein einfaches

Code: Alles auswählen

player.play(Phonon.MediaSource(QtCore.QUrl("http://www.youtube.com/watch?v=XZ5TajZYW6Y")))
wird es wohl nicht tun.

Grüße,
anogayales
lunar

@anogayales: Lies bitte, was ich schreibe. Du kannst den bottle-Server durchaus in einem eigenen Thread, nur Widgets dürfen nicht außerhalb des Haupt-Threads verwendet werden. An das in das Dokument eingebettete QObject gelangst nicht innerhalb von bottle, wohl aber im Javascript der geladenen Seite. Was deets ebenfalls schon gesagt hat...

Was das Streamen des Videos angeht, so verrät Dir eine Google-Suche oder ein Blick in den Quelltext von Youtube-Clients wie youtube-dl oder Minitube, wie Du eine URL erhältst, welche direkt auf das Video verweist.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Gut, mitlerweile bin ich an die entsprechende URL gekommen. Leider bekomm ich von Phonon kein Feedback warum etwas nicht abgespielt werden konnte.

Wenn ich die entsprechende URL im Browser eingebe bekomm ich einen entsprechenden Download. Hab das ganze mal über urllib2 abgegriffen:
data.info().values()

Code: Alles auswählen

['39465300',
 'nosniff',
 'bytes',
 'Sat, 07 Jan 2012 15:02:15 GMT',
 'gvs 1.0',
 'Thu, 30 Dec 2010 07:54:56 GMT',
 'close',
 'private, max-age=21926',
 'Sat, 07 Jan 2012 15:02:15 GMT',
 'video/x-flv']
Kann vielleicht mein Packend nicht mit x-flv umgehen, oder sendet Phonon vielleicht die falschen Header?

URL:

Code: Alles auswählen

http://o-o.preferred.fra02s03.v19.lscache1.c.youtube.com/videoplayback?sparams=i
d%2Cexpire%2Cip%2Cipbits%2Citag%2Csource%2Calgorithm%2Cburst%2Cfactor%2Ccp&fexp=
907306&algorithm=throttle-factor&itag=35&ip=91.0.0.0&burst=40&sver=3&signature=C
8B1C1F9623346340254958AFBE17FB62A5BF2CD.872036D3DE26C1ECCB4C67A0141D7B8CC1671234
&source=youtube&expire=1325970761&key=yt1&ipbits=8&factor=1.25&cp=U0hRSllSTl9LS0
NOMV9RTFJIOlJVSW9qRGNVdWJC&id=d333051cb492345c
Btw, das Streamen der Daten scheint sowieso gegen die TOA zu gehen.

Zurück zum JavaScript:
Ich versteh es immer noch nicht, das einschleusen eines QObjects in das JavasScript erlaubt mir doch nur, von der Sicht des Frameworks, Daten zu empfangen, aber nicht Anfragen aufzugeben.

Müsste das Ganze also so aussehen?:

Code: Alles auswählen

class WebServer(QtCore.QThread):
 
    def run(self):
        debug()
        run(host='localhost', port=8080, reloader=True)

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    widget, frame = gui()
    webserver = WebServer()
    webserver.start()
    sys.exit(app.exec_())
Wobai gui() die gui initalisiert. Mir ist immer noch nicht klar wie ich mit bottle auf dem frame Objekt arbeiten kann.

Das hier funktioniert leider nicht:

Code: Alles auswählen

@route('/api/youtube/')
def youtube():
    print "play"
    frame.evaluateJavaScript("ytplayer.playVideo()")
Die Methode wird zwar ausgeführt, aber das Video spielt sich nicht ab. Aus dem GUI Thread aufgerufen klappt das aber problemlos.

Grüße,
anogayales
deets

Ich glaube, du gehst das falsch an. Bottle ist doch ueberhaupt nicht die richtige Stelle, um irgendwas auszuloesen. Das dient lediglich dazu, dem Webview vorzugaukeln, dass er einen Server hat. Sonst sind doch alle events aller Art doch entweder in Qt, oder im JS-Code. Und damit kannst du entweder mit einem QObject interagieren, oder halt AJAX-Requests absetzen, die dann wieder in bottle auflaufen.

Aber *VON* bottle kommt doch nix...
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Ja, wie würdest du den folgenden UseCase modelieren?

Benutzer A ruft URL /play im Browser aus.
Hostrechner spielt Musikstück B ab.

Da _muss_ doch was von bottle ausgelöst werden? Wie kann den Qt sonst wissen, dass Benutzer A ein Musikstück abgespielt haben will?

Grüße,
anogayales
lunar

@anogayales: Mag sein, dass Streamen gegen die TOS geht, doch es gibt jedenfalls Anwendungen, die streamen, also wird das für Dich schon so schlecht nicht sein. Woran es liegt, dass der Stream nicht abgespielt wird, kann ich Dir so ohne Weiteres nicht sagen.

Bottle kann da gar nichts auslösen, denn bottle hat überhaupt keinen Zugriff auf die clientseitigen Steuerelemente. Auf dieser kannst Du über den Umweg eines eingeschleusten Objekts nur und ausschließlich client-seitig zugreifen, also im Javascript-Code der von bottle zurückgegebenen Seite. Dort und nur dort kannst Du die Qt-Anwendung über das erwähnte Objekt kontrollieren und ihr mitteilen, welches Video wie abzuspielen ist. Du musst also Javascript-Code schreiben, bottle verbleibt nichts anderes zu tun, als bei einem Request eine HTML-Seite mit entsprechendem, eingebettetem Javascript-Quelltext zurückzugeben.

Nichts für ungut, doch diese Zusammenhänge sind eigentlich Grundwissen bei einem solchen Projekt. Solange Dir die Trennung zwischen Client und Server nicht klar ist, und Du nicht weißt, wo bestimmter Code ausgeführt wird und wo bestimmte Objekte leben, brauchst Du gar nicht erst zu anzufangen.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

lunar hat geschrieben: Bottle kann da gar nichts auslösen, denn bottle hat überhaupt keinen Zugriff auf die clientseitigen Steuerelemente.
Ich weiß nicht ob du mich nicht verstehst, aber Bottle soll rein gar nichts auf dem Client ausführen. Bottle soll lediglich ein youtube video abspielen.
Das ganze könnte ich auch über:

Code: Alles auswählen

@route('/api/youtube/:id')
def youtube(id):
    import webbrowser
    youtube_url = "http://www.youtube.com/watch?v=%s" % id
    webbrowser.open(youtube_url)
auslösen.
Damit habe ich aber keine Kontrolle darüber, wie weit das Video bereits abgespielt ist. Ich kann das Video nur pausieren, indem ich den Prozess abschiess, usw. Ich möchte lediglich die Youtube API benutzen und nicht stupide Browserfenster auf meinem Server öffnen lassen.

Grüße,
anogayales
lunar

@anogayales: Lies die Beiträge nochmal, und zwar bitte richtig. Niemand hat gesagt, dass bottle Code beim Client ausführen soll, dass ist schließlich unmöglich.

Wir haben vielmehr versucht, Dir zu erklären, dass bottle überhaupt nicht kontrollieren kann, wie das Video abgespielt wird, da bottle selbst das Video gar nicht abspielt. Das geschieht vielmehr beim Client im entsprechenden QWebView-Steuerelement, auf welches bottle wie mehrfach erklärt keinen Zugriff hat. Zur Kommunikation zwischen dem Youtube-Player und Deiner Desktop-Anwendung kannst Du mithin nur Code verwenden, der auch beim Client, also im Prozess der Desktop-Anwendung ausgeführt wird. Im Falle einer Webanwendung gilt das nur für in die HTML-Seite eingebettetes Javascript, mit entsprechenden eingeschleusten Objekten, welche eine Kontrolle Deiner Qt-Anwendung durch Javascript ermöglichen.

bottle ist da eigentlich auch überflüssig, da Du den Youtube-Player auch einfach in einer statischen Website einbetten könntest.

Die ganze Angelegenheit wäre für beide Seiten hier erheblich einfacher, wenn Du Dir vorher erst einmal die erwähnten Grundlagen klar machen würdest.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

lunar hat geschrieben: Wir haben vielmehr versucht, Dir zu erklären, dass bottle überhaupt nicht kontrollieren kann, wie das Video abgespielt wird, da bottle selbst das Video gar nicht abspielt.
In meinem Proof-of-Concept oben via webbrowser funktionert es doch. Da wird das Video von bottle aus gestartet und das auf dem Hostrechner, so wie es sein soll.
lunar hat geschrieben:
Das geschieht vielmehr beim Client im entsprechenden QWebView-Steuerelement, auf welches bottle wie mehrfach erklärt keinen Zugriff hat.
Nein, nein und nochmals nein. Das QWebView läuft doch in dem Prozess des Server. Der Client ist derjenige der die URL im Browser aufruft.

Sorry, aber vielleicht habe ich mich einfach missverständlich ausgedrückt. Ich wollte doch einfach nur eine Webanwendung schreiben, die auf dem Server ein youtube video startet. Der Client bekommt davon natürlich nur was mit, wenn er in der Nähe des Hostrechners ist. Siehe use case oben. Die Webview wird nur benötigt um mit der Youtube Player API zu kommunizieren.


Was habe ich nur getan :twisted:

Grüße,
anogayales
deets

Was heisst den "youtube video auf dem server starten"? Wer schaut das denn da? Ein youtube-video laueft *IMMER* in einem webclient - nie auf einem webserver. Natuerlich kannst du einen Rechner ("Server") haben, auf dem du einen browser startest, und einen web-server. Aber darum geht es hier doch nicht, oder?

ALso, nochmal: was ist bei dir client, was ist server, wo laeuft das video, wer schaut es sich an?
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@anogayales:
Was passiert Deiner Meinung nach hier?

Code: Alles auswählen

@route('/api/youtube/:id')
def youtube(id):
    import webbrowser
    youtube_url = "http://www.youtube.com/watch?v=%s" % id
    webbrowser.open(youtube_url)
Da spielt nicht bottle das Video ab, sondern bottle startet den Browser, der als Webclient das Video holt. Wenn Du jetzt einen Schritt weiter gehst und bottle selbst die Videodaten holen lässt, dann ist bottle gegenüber youtube Client und nicht Server.
Ich glaube die Kommunikation scheitert hier gerade an diesen Begrifflichkeiten.

Wozu Du bottle zwischen Qt und youtube brauchst, ist mir schleierhaft. Soll das eine Art Proxysystem werden?
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Ich will die Musik in meinem Wohnheim fernsteuern. Dabei soll es möglich sein, über eine Server URL ein entsprechendes Video zu laden (via Smartphone) und dieses wird dann vom Hostrechner im Wohnheim mittels Lautsprecher ausgestrahlt.

Ich glaub auch, dass es an den Begrifflichkeiten von Server/Client scheitert bzw. mitlerweile habe ich wohl euer Problem verstanden. Ich sehe mittlerweile auch das der Bottle Ansatz nicht funktioniert. Bottle hat keine Möglichkeit auf Signale von Qt zu reagieren.

Wäre hier ein QTcpServer von Qt vielleicht besser? Hier kann ich nämlich bequem Signale und Slots benutzen. Nachteil ist wohl, dass man nicht über HTTP direkt Daten einschleusen könnte ohne viel Aufwand (parsen) zu betreiben?

Grüße,
anogayales
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

anogayales hat geschrieben:Ich will die Musik in meinem Wohnheim fernsteuern. Dabei soll es möglich sein, über eine Server URL ein entsprechendes Video zu laden (via Smartphone) und dieses wird dann vom Hostrechner im Wohnheim mittels Lautsprecher ausgestrahlt.
Ich verstehe das so:
1. Der Hostrechner soll youtube-Videos abspielen und den Sound ausgeben.
Dafür kannst Du QWebkit mit Flash resp. HTML5 nutzen oder Phonon direkt. Für HTML5 bzw. Phonon solltest Du die Links für die Mpeg-Version nehmen, nicht die Flash-Links (worauf das x-flv hindeutet). Wobei ich nicht weiss, ob Phonon .flv mit ensprechendem Plugin versteht.

2. Der Player soll programmatisch steuerbar sein.
Das geht bei QWebkit über JS und bei Phonon direkt.

3. Die Steuerung soll über eine HTTP-Schnittstelle erreichbar sein.
Hierfür kannst Du in der Tat bottle verwenden.

Ich gehe davon aus, dass der Player nur eine Instanz haben soll. Ausgehend von QWebkit und Flash (womit Du den youtube-Standard- oder chromeless-Player verwenden kannst) strickst Du Dir einfach eine Qt-Klasse, die eine Video-URL entgegen nehmen kann und daraufhin abspielt (über QWebView). Die Steuerung bindest Du an die JS-API des Players an und stellt entsprechende Methoden wie skip, stop etc zur Verfügung. Diese Klasse packst Du in einen XML-RPC-Server mit entsprechenden Request-Methoden (Achtung: auf Threadsicherheit achten!)
Dann baust Du Dir eine bottle-Umgebung mit den Unterseiten für die Steuerung. In den einzelnen Requesthandlern verweisst Du auf die entsprechende Methode des XML-RPC-Servers.
Das schöne an der XML-RPC-Lösung ist die einfache Erweiterbarkeit. So könntest Du z.B. einen XML-RPC-Client fürs Smartphone bauen, der direkt mit dem Player ohne Umweg übers Web interagiert. (Der bottle-HTTP-Server wäre dann nur ein möglicher Client des XML-RPC-Servers.)

Mit einigen Verrenkungen könntest Du bottle und den Player auch direkt verheiraten. Allerdings dürfte der Aufwand, das globale Playerobjekt threadsicher über die Requesthandler zu steuern ungemein höher sein und schränkt zusätzlich die Wahl der HTTP-Server für bottle ein.

Last but not least kannst Du mit QTcpServer die Serverfunktionalität selbst vorhalten. Das ist dann ziemlich low level aber vllt. ausreichend, falls Du nur den Player steuern und keine aufwendigen Seiten ausliefern willst.
deets

anogayales hat geschrieben:Ich will die Musik in meinem Wohnheim fernsteuern. Dabei soll es möglich sein, über eine Server URL ein entsprechendes Video zu laden (via Smartphone) und dieses wird dann vom Hostrechner im Wohnheim mittels Lautsprecher ausgestrahlt.
Haleluja! Nach nur 15 posts endlich mal ne Ansage, worum es ueberhaupt geht...

Was du willst, ist also ein fernsteuerbarer Videoplayer. Ok. Also baust du zuerstmal einen Videoplayer. ZB mit Qt + der eingebetteten VLC-Komponente oder was auch immer Youtube videos spielen kann. Und der hat einen SLOT, wenn man da ein youtube-video-link reinjagd, dann spielt der den ab.

Und dann kannst du natuerlich schon bottle in einem eigenen Thread verwenden, um diesen Player - der dann aber *IMMER* laeuft - auch noch eine Webapp hosten zu lassen, die dann per smartphone aufgerufen werden kann. Und wenn du da ein URL eingibst oder wie auch immer, dann kann natuerlich bottle das via SIGNAL an den Qt-Prozess melden, an den oben beschrieben SLOT.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Vielen Dank für eurer Antworten. Da werde ich ein Weilchen beschäftigt sein.

Mir sagt momentan die Phonon Variante zu. Habe auch rausgefunden wie man an den MP4 Stream kommt. Leider hat damit mein Phonon auf Windows Probleme. Der Player geht immer in den Phonon::ErrorState http://developer.qt.nokia.com/doc/qt-4. ... State-enum nachdem ich meine Youtube mp4 Datei abgespielt habe, sowohl mit der komplett runtergeladenen Datei als auch mit dem http Link. Im VLC und Windows Media Player klappt das Abspielen problemlos. Dummerweise kann man nicht so einfach das Phononbackend in Windows wechseln, obwohl mp4 eindeutig von Phonon unterstützt wird.

Ein

Code: Alles auswählen

    for mime in Phonon.BackendCapabilities.availableMimeTypes():
        print mime
gibt

Code: Alles auswählen

application/vnd.ms-wpl
application/x-mplayer2
...
audio/mp4
...
video/mp4
...
video/x-msvideo
vnd.ms.wmhtml
zurück. Alles also seeehhhhr seltsam.

Eine ffmpeg info Abfrage verrät mir:

Code: Alles auswählen

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'videoplayback.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isomavc1mp42
    creation_time   : 2010-12-29 07:58:20
  Duration: 00:04:23.20, start: 0.000000, bitrate: 656 kb/s
    Stream #0:0(und): Audio: aac (mp4a / 0x6134706D), 44100 Hz, stereo, s16, 114
 kb/s
    Metadata:
      creation_time   : 2010-12-29 07:58:20
      handler_name    : (C) 2007 Google Inc. v08.13.2007.
    Stream #0:1(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yu
v420p, 480x360 [SAR 1:1 DAR 4:3], 539 kb/s, 25 fps, 25 tbr, 25k tbn, 50 tbc
    Metadata:
      creation_time   : 2010-12-29 07:58:20
      handler_name    :
Edit: Noch eine Frage:
deets hat geschrieben:dann kann natuerlich bottle das via SIGNAL an den Qt-Prozess melden, an den oben beschrieben SLOT.
Wie kann ich Signale über Prozessgrenzen senden?

Grüße,
anogayales
Antworten