Reproductor de audio de YouTube con PyQt 4

Versión: 2.x, 3.x.
Descargas: ytplayer.zip.

Vista previa

Se trata de una pequeña aplicación multiplataforma capaz de reproducir cualquier video alojado en youtube.com. En base a este código, pueden observarse las diversas características que provee Qt, específicamente el módulo QtWebKit que soporta por completo el reproductor de Adobe Flash y permite interactuar con código de JavaScript fácilmente.

El programa consiste en cargar una determinada URL en un navegador que se encuentra oculto, utilizando la clase QWebView. Luego, acceder al conjunto de funciones API de YouTube en JavaScript para controlar el reproductor (más información de JavaScript y Qt en este enlace). En un segundo plano, utilizando los hilos estándares que provee la librería, se hacen las llamadas correspondientes para actualizar constantemente el indicador del tiempo de la reproducción.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#       ytplayer.py
#
#       Copyright 2014 Recursos Python - www.recursospython.com
#
#


import sys
from datetime import timedelta
from functools import partial
from time import sleep

from PyQt4.QtGui import QApplication

from PyQt4.QtCore import (pyqtSignal, pyqtSlot, QMetaObject, Qt,
                          QThread, QUrl)
from PyQt4.QtGui import (QLabel, QLineEdit, QMainWindow, QMessageBox,
                         QPushButton, QSlider, QWidget)
from PyQt4.QtWebKit import QWebView, QWebSettings


STATE_UNSTARTED = -1
STATE_ENDED = 0
STATE_PLAYING = 1
STATE_PAUSED = 2
STATE_BUFFERING = 3
STATE_VIDEO_CUED = 5


class Thread(QThread):
    """General async worker."""
    update_video_time = pyqtSignal()
    
    def run(self):
        while True:
            # Update GUI label.
            self.update_video_time.emit()
            sleep(1)


class MainWindow(QMainWindow):
    
    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle("YouTube Customizable Player")
        self.central_widget = QWidget(self)
        
        # Hidden web browser settings.
        self.webview = QWebView()
        QWebSettings.globalSettings().setAttribute(
            QWebSettings.PluginsEnabled, True)
        
        self.state_button_texts = {
            STATE_UNSTARTED: u"▶",
            STATE_ENDED: u"▶",
            STATE_PAUSED: u"▶",
            STATE_PLAYING: u"▮▮",
            STATE_BUFFERING: u"↑↓",
            STATE_VIDEO_CUED: u"..."
        }
        
        # YouTube video URL.
        self.video_text = QLineEdit(self)
        self.video_text.setGeometry(10, 10, 280, 25)
        
        self.load_button = QPushButton("Load", self)
        self.load_button.setGeometry(300, 10, 90, 25)
        self.load_button.clicked.connect(self.load_video)
        
        # Volume slider.
        QLabel("Volume: ", self).setGeometry(10, 60, 80, 25)
        self.volume_slider = QSlider(Qt.Horizontal, self)
        self.volume_slider.setRange(0, 100)
        self.volume_slider.sliderMoved.connect(self.set_volume)
        self.volume_slider.setGeometry(70, 60, 150, 25)
        
        # Play / pause button.
        self.state_button = QPushButton(self)
        self.state_button.setGeometry(10, 100, 50, 50)
        self.state_button.setStyleSheet("font-size: 37px;")
        self.state_button.clicked.connect(self.state_button_clicked)
        self.state_button.setEnabled(False)
        
        # Video time.
        self.duration_label = QLabel(self)
        self.duration_label.setGeometry(290, 105, 100, 20)
        self.duration_label.setAlignment(Qt.AlignRight)
        
        self.slider_pressed = False
        self.slider = QSlider(Qt.Horizontal, self)
        self.slider.setGeometry(70, 113, 320, 25)
        self.slider.sliderReleased.connect(self.seek_to_ahead)
        self.slider.sliderMoved.connect(self.seek_to)
        self.slider.setEnabled(False)
        
        # Background worker.
        self.thread = None
        
        self.resize(400, 170)
        self.setCentralWidget(self.central_widget)
    
    def get_video_state(self):
        return self.webframe.evaluateJavaScript(
            "player.getPlayerState()").toInt()[0]
    
    @pyqtSlot(int)
    def js_player_state_changed(self, new_state):
        self.state_button.setText(self.state_button_texts[new_state])
    
    @pyqtSlot(str, int, int)
    def js_set_video_data(self, title, duration, volume):
        self.setWindowTitle(title)
        
        # Seconds to hh:mm:ss.
        self.duration_label.setText(
            "0:00:00 / " + str(timedelta(seconds=duration))
        )
        
        # Prepare slider.
        self.slider.setRange(0, duration)
        self.slider.setValue(0)
        self.slider.setEnabled(True)
        self.volume_slider.setValue(volume)
        
        self.state_button.setEnabled(True)
        
        # Start background worker.
        self.thread = Thread()
        self.thread.update_video_time.connect(
            self.thread_update_video_time)
        self.thread.start()
    
    def load_finished(self):
        # Python - JavaScript bridge.
        add_py_obj = partial(
            self.webframe.addToJavaScriptWindowObject, "app", self
        )
        add_py_obj()
        self.webframe.javaScriptWindowObjectCleared.connect(add_py_obj)
        
        # General YouTube API stuff.
        self.webframe.evaluateJavaScript("""
            function onYouTubePlayerReady(playerId)
            {
                // Get the "movie_player" HTML element.
                player = document.getElementById('movie_player');
                
                // Add events handlers.
                player.addEventListener('onStateChange',
                                        'onPlayerStateChange');
                
                app.js_set_video_data(
                    player.getVideoData().title,
                    player.getDuration(),
                    player.getVolume()
                );
            }
            
            // Callback functions.
            function onPlayerStateChange(newState)
            {
                app.js_player_state_changed(newState)
            }
        """)
    
    def load_video(self):
        # Only YouTube videos allowed.
        if "youtube.com" not in str(self.video_text.text()).lower():
            QMessageBox.information(self, "Error",
                "Just YouTube videos!")
            return
        
        # Exit previous thread, if necessary.
        if self.thread is not None:
            self.thread.exit()
        
        try:
            if self.get_video_state() == STATE_PLAYING:
                self.stop_video()
        except AttributeError:
            # Web browser not loaded yet, just ignore it.
            pass
        
        # Load URL.
        self.webview.load(QUrl(self.video_text.text()))
        self.webframe = self.webview.page().mainFrame()
        self.webframe.loadFinished.connect(self.load_finished)
        
        self.state_button.setEnabled(False)
        self.slider.setEnabled(False)
    
    def pause_video(self):
        self.webframe.evaluateJavaScript("player.pauseVideo()")
    
    def play_video(self):
        self.webframe.evaluateJavaScript("player.playVideo()")
    
    def seek_to(self, seconds, allow_seek_ahead=False):
        # Pause video while dragging slider.
        if not allow_seek_ahead:
            if self.get_video_state() == STATE_PLAYING:
                self.pause_video()
        
        # Call JavaScript API function.
        self.webframe.evaluateJavaScript(
            "player.seekTo({0}, {1})".format(
                seconds, str(allow_seek_ahead).lower()
            )
        )
        
        # Resume when done.
        if allow_seek_ahead:
            self.play_video()
    
    def seek_to_ahead(self):
        self.seek_to(self.slider.value(), True)
    
    def set_volume(self, volume):
        self.webframe.evaluateJavaScript(
            "player.setVolume({0})".format(volume))
    
    def state_button_clicked(self):
        state = self.get_video_state()
        
        if (state == STATE_UNSTARTED or state == STATE_ENDED or
            state == STATE_PAUSED):
            self.play_video()
        elif state == STATE_PLAYING:
            self.pause_video()
    
    def stop_video(self):
        self.webframe.evaluateJavaScript("player.stopVideo()")
    
    @pyqtSlot()
    def thread_update_video_time(self):
        # Get current video time and update GUI label.
        current_time = self.webframe.evaluateJavaScript(
            "player.getCurrentTime()")
        
        # Python 3 compatibility.
        if isinstance(current_time, float):
            current_time = int(current_time)
        else:
            current_time = current_time.toInt()[0]
        
        self.duration_label.setText(
            str(timedelta(seconds=current_time)) +
            self.duration_label.text()[7:]
        )
        self.slider.setValue(current_time)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

Curso online 👨‍💻

¡Ya lanzamos el curso oficial de Recursos Python en Udemy! Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.

Consultoría 💡

Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.

3 comentarios.

Deja una respuesta