본문 바로가기
  • 우당탕탕속의 잔잔함
Programming/Raspberry Pi

[QT] Python을 이용한 Video Player 개발

by zpstls 2023. 3. 21.
반응형

이번 포스트는 Python을 기반으로 여러 가지 Video를 플레이하는 프로그램을 만들어보도록 하겠습니다.

그리고 이를 Raspberry Pi에서 플레이해보도록 할 것입니다.

 

 

우선, 개발하고자 하는 프로그램에 대해 간략히 설명해 보도록 하겠습니다.

Program Flow

프로그램을 실행시키면 0번 영상과 0번 사운드가 플레이됩니다. 그리고 키보드 I 또는 O를 클릭하면 다음 영상 또는 이전 영상이 플레이됩니다. 이때 맨 처음 영상일 때는 O를 눌러도 이전 영상이 아닌 처음 영상이 플레이되어야 하며, 마지막 영상의 경우는 I를 누르면 처음 영상으로 넘어가야 합니다. 또한 사운드의 경우는 0번 영상부터 2번 영상까지는 0번 사운드가, 3번 영상부터 14번 영상까지는 1번 사운드가 이후 영상부터 마지막 영상까지는 2번 사운드가 플레이되어야 합니다. 이러한 방식으로 무산 루프를 돌며 플레이되다가 End 버튼을 누르면 프로그램이 종료됩니다.

핵심은 영상과 사운드의 전환과 제어 기능을 부여하여 개발해야 한다는 것입니다.

 

 

 

 

 

그럼 이제 본격적으로 개발을 진행해보겠습니다!

 

Video Player를 개발하는 방법은 여러 가지가 있을 것입니다. 뭐... OpenCV를 이용해서 개발하는 방법도 있을 것이고 MFC를 통해 개발해도 좋고요. 그러나 저는 Python환경에서 PyQt와 VLC를 이용해 진행해 볼 것입니다. 

당연한 이야기지만, Python과 pyqt5, VLC가 설치되어 있어야 합니다. PyQt5VLC의 경우 다음과 같은 명령어를 통해 설치할 수 있습니다.

>> pip3 install PyQt5
>> pip install python-vlc

 

우선, 플레이할 영상과 사운드 데이터는 VideoListSoundList Directory에 저장해 놓습니다. 순서대로 a.mp4, b.mp4, ..., 0.wav, 1.wav, 2.wav 이런 식으로 말이죠.

Data

 

메인 코드는 다음과 같습니다.

def main():
    app = QtWidgets.QApplication(sys.argv)
    player = VideoPlayer()
    player.showFullScreen()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

단순합니다. QT Widget을 생성하고 해당 위젯에서 구동될 VideoPlayer라는 클래스를 Load 한 후 사용하다가 Class에서 종료신호를 주면 Widget을 해제합니다. 참고로 showFullScreen()을 통해 위젯을 전체화면으로 띄웁니다.

 

다음은 Video Player의 전역변수와 init 부분입니다.

class VideoPlayer(QtWidgets.QMainWindow):

    clip_start_sig = False;
    video_list = []
    video_index = 0
    sound_list = []
    sound_index = -1
    button_pushed_sig = False
    button_sound_player = None

    def __init__(self, master=None):
        QtWidgets.QMainWindow.__init__(self, master)

        # Create a basic vlc instance
        self.instance = vlc.Instance()
        self.media = None
        self.instance_sound = vlc.Instance()
        self.media_sound = None

        # Create an empty vlc media player
        self.mediaplayer = self.instance.media_player_new()
        self.sound_player = self.instance_sound.media_player_new()

        self.create_ui()
        self.is_paused = False

Video와 Sound 리스트를 관리하고 플레이 상황을 관리하기 위한 Index 데이터, Button Event가 발생되었는지 판단하기 위한 변수들이 전역변수로 위치합니다. 그리고 Video와 Sound 플레이를 위한 각각의 instance를 생성하고 초기화해 주는 작업을 수행합니다.

 

위 코드 중에서 UI를 구성해 주는 함수가 있는데, 해당 부분은 다음과 같습니다.

def create_ui(self):
    self.widget = QtWidgets.QWidget(self)
    self.setCentralWidget(self.widget)

    # In this widget, the video will be drawn
    if platform.system() == "Darwin": # for MacOS
        self.videoframe = QtWidgets.QMacCocoaViewContainer(0)
    else: # the others
        self.videoframe = QtWidgets.QFrame()

    self.palette = self.videoframe.palette()
    self.palette.setColor(QtGui.QPalette.Window, Qt.black)
    self.videoframe.setPalette(self.palette)
    self.videoframe.setAutoFillBackground(True)

    self.vboxlayout = QtWidgets.QVBoxLayout()
    self.vboxlayout.addWidget(self.videoframe)
    self.widget.setLayout(self.vboxlayout)
    
    self.timer = QtCore.QTimer(self)
    self.timer.setInterval(100)
    self.timer.timeout.connect(self.update_ui)

    # Main Program
    self.play_video_list()
    self.play_video()


def update_ui(self):
    # No need to call this function if nothing is played
    if not self.mediaplayer.is_playing():
        self.timer.stop()


def play_video_list(self):
    self.video_list = glob.glob("VideoList/*")
    self.video_list = sorted(self.video_list)
    print(self.video_list)
    self.sound_list = glob.glob("SoundList/*")
    self.sound_list = sorted(self.sound_list)
    print(self.sound_list)

        if not self.is_paused:
            self.stop()

위젯을 중앙에 생성하고 영상을 Draw 할 공간을 만들어줍니다. 빈 공간은 자동을 채워주도록 하고 Update를 수행할 때의 Interval 값을 설정해 줍니다. 이때의 Update는 각각의 영상 플레이가 완료되었을 때의 처리를 위한 것입니다.

전반적으로 아래와 같이 영상이 나오게 됩니다.

MainWindow

그리고 플레이할 영상과 사운드 데이터의 파일명을 Read 하고 순서대로 정렬해서 저장합니다. 이는 재생 시 순서를 올바르게 해 주기 위한 작업입니다.

 

다음은 영상의 전환을 위한 Keyboard Event을 구현합니다.

def keyPressEvent(self, e):
    # exit program
    if e.key() == Qt.Key_Escape:
       self.close()

    # next video
    if e.key() == Qt.Key_I:
        # Button Sound
        self.button_sound_player = vlc.MediaPlayer("ButtonSound/button.wav")
        self.button_sound_player.play()

        self.button_pushed_sig = True
        if self.video_index == 0 :
            self.clip_start_sig = True
        self.stop()

    # previous video
    elif e.key() == Qt.Key_O:
        # Button Sound
        self.button_sound_player = vlc.MediaPlayer("ButtonSound/button.wav")
        self.button_sound_player.play()

        self.button_pushed_sig = True
        if self.clip_start_sig == True :
            self.video_index -= 2

            if self.video_index == -2 :
                self.video_index = len(self.video_list) - 2
        self.stop()

Keyboard Event가 발생되면 해당 이벤트에 맞게 영상이 플레이되도록 합니다. 기본적으로는 영상과 사운드의 Index를 적절한 값으로 변화시켜 주는 작업을 수행합니다.

위와 같이 버튼이 눌렸을 때 효과음을 주기 위해 단기적으로 MediaPlayer를 구성해 플레이해 줄 수도 있을 것입니다.

 

다음은 영상과 사운드 데이터를 순서대로, 또는 키보드 이벤트에 맞게 플레이하기 위한 부분입니다.

def play_video(self):
    self.media = self.instance.media_new(self.video_list[self.video_index])
    self.mediaplayer.set_media(self.media)
    self.media.parse()

    if platform.system() == "Linux": # for Linux using the X Server
        self.mediaplayer.set_xwindow(int(self.videoframe.winId()))
    elif platform.system() == "Windows": # for Windows
        self.mediaplayer.set_hwnd(int(self.videoframe.winId()))
    elif platform.system() == "Darwin": # for MacOS
        self.mediaplayer.set_nsobject(int(self.videoframe.winId()))

    # Sound - each clip
    if self.video_index < 3 :
        if self.sound_index != 0:
            self.sound_index = 0
            self.media_sound = self.instance_sound.media_new(self.sound_list[self.sound_index])
            self.sound_player.set_media(self.media_sound)
            self.media.parse()
            self.sound_player.stop()
            self.sound_player.play()

    elif self.video_index >= 3 and self.video_index < 15:
        if self.sound_index != 1 :
            self.sound_index = 1
            self.media_sound = self.instance_sound.media_new(self.sound_list[self.sound_index])
            self.sound_player.set_media(self.media_sound)
            self.media.parse()
            self.sound_player.stop()
            self.sound_player.play()

    elif self.video_index >= 14 :
        if self.sound_index != 2 :
            self.sound_index = 2
            self.media_sound = self.instance_sound.media_new(self.sound_list[self.sound_index])
            self.sound_player.set_media(self.media_sound)
            self.media.parse()
            self.sound_player.stop()
            self.sound_player.play()

    if not self.sound_player.is_playing():
        self.sound_player.stop()
        self.sound_player.play()

    self.mediaplayer.play()
    self.timer.start()
    self.is_paused = False


def stop(self):
   if self.button_pushed_sig == True or self.video_index == 18 :
       self.button_pushed_sig = False

       if self.clip_start_sig == True:
           # start to next video
           self.video_index += 1
           if self.video_index >= len(self.video_list):
                self.video_index = 0
                self.clip_start_sig = False

       if self.video_index == 0 :
           self.clip_start_sig = False

       self.play_video()

솔직히 영상의 경우는 그냥 순서대로 플레이해 주면 됩니다. 그러나 사운드의 경우는 영상의 번호에 종속되어 있으므로 특정 인덱스에 따라 처리해 줄 필요가 있습니다.

뭐... 해당 부분은 구현하고자 하는 필요에 따라 적절히 변경되어야 할 부분일 것입니다.ㅎㅎ

 

 

이와 같이 정말 간단하게 구현할 수 있었습니다.

길게 늘여 써서 그렇지 코드를 정리한다면 100줄 정도로 구현할 수 있지 않을까요?

뭐... 위 방식은 자원을 최적화한 코드가 아니기에 코드 정리가 필연적으로 필요하긴 합니다.

 

참고로 Raspberry Pi에 LCD를 연결해서 플레이시킨다면 꽤 쓸만한 슬라이드 쇼나 전자 액자 또는 홍보 디바이스로 활용해 볼 수 있을 것 같습니다.

따라서 다음 포스트에서는 Raspberry Pi에서 이 프로그램을 구동시켜 활용하는 방법을 다뤄보도록 하겠습니다.

 

이번 포스트는 여기서 마무리하도록 하겠습니다.

 

 

PS. Raspberry Pi에서 Video Player를 적용하는 부분에 대한 내용은 다음 포스트를 참고해주세요.

 

- Raspberry Pi 초기 Setting

 

[Environment] Raspberry Pi 초기 Setting

이번 포스트는 Raspberry Pi를 제대로 사용하기 위한 초기 Setting과정을 다뤄볼 것입니다. 우선, HW를 구성해야 하겠죠...? 뭐... 구성하고자 하는 목적에 따라 조금씩 다르겠지만, 라즈베리파이를 PC처

mj-thump-thump-story.tistory.com

 

- Raspberry Pi + Button(Shutdown, Reboot, Keyboard Event)

 

[GPIO] Raspberry Pi에 스위치/버튼 연결 후 Shutdown/Reboot, Keyboard Event 수행

이번 포스트는 Raspberry Pi에 Button(Switch)를 부착하고 이를 활용하는 방법에 대해 다뤄보고자 합니다. 우선, 일전에 다뤄보았던 Video Player에 활용될 수 있는 사항이니 궁금하시다면 참고해 주세요. [

mj-thump-thump-story.tistory.com

 

- Raspberry Pi Mouse Curser 제어

 

[Mouse] Raspberry Pi에서 마우스 커서 제어

이번 포스트는 기본적으로 표시되는 마우스 커서를 없애는 방법에 대해 다룰 예정입니다. 마우스커서가 있어야 마우스를 통해 아이콘이나 버튼 등을 클릭할 수 있을 것입니다. 내가 지시하고

mj-thump-thump-story.tistory.com

반응형

댓글