파이썬 예제 (음악 플레이어)

이번에 만들어 본 Music Player 입니다.

[뮤직 플레이어 실행화면]

Python + PyQt5를 이용해 만들어 보았으며, 주요 기능은 아래와 같습니다.

1. 음악 파일 리스트 기능 (추가, 삭제)

2. 다양한 오디오 파일 재생 (*.wav, *.mp3, *.flac, *.ogg, 단, 코덱 필요)

3. 재생, 정지, 일시정지, 다음곡, 이전곡 재생

4. 볼륨 제어

5. 재생 옵션 (현재 한곡만, 현재 한곡 반복, 순차, 전체 반복, 랜덤)

6. 재생 진행 바를 통한 진행률 표시

구글을 통해 검색해봐도 역시나 마음에 드는 예제는 못 찾는 건지, 제 눈에는 잘 보이지 않습니다.

찾은 예제들이 단순 mp3 파일 한곡 재생 예제 밖에 없어, 한번 만들어 보았습니다.

만들고 보니 아주 쓸만하게 잘 만들어져 저도 즐겨 쓰고 있습니다.



그럼 바로 본론으로 들어가 보겠습니다.

소소코드는 2개의 파일(main.py, player.py) 로 구성되어 있습니다.

음악 플레이어는 아래 main.py 파일과 마지막에 설명하는 player.py 파일 두개만 복사해 파이썬 코드로 붙여넣기 하면 바로 실행됩니다.

당연히 main 함수의 역할을 담당하는 main.py가 파이썬 시작파일로 설정되어야 합니다.

먼저 main.py 파일의 코드를 살펴보겠습니다.

from PyQt5.QtWidgets import *
import sys
from player import *

QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)

class CWidget(QWidget):   

    def __init__(self):
        super().__init__()
        self.player = CPlayer(self)
        self.playlist = []
        self.selectedList = [0]
        self.playOption = QMediaPlaylist.Sequential

        self.setWindowTitle('Ocean Coding School')
        self.initUI()

    def initUI(self):

        vbox = QVBoxLayout()        

        # 1.Play List
        box = QVBoxLayout()
        gb = QGroupBox('Play List')
        vbox.addWidget(gb)
        
        self.table = QTableWidget(0, 2, self)             
        self.table.setHorizontalHeaderItem(0, QTableWidgetItem('Title'))
        self.table.setHorizontalHeaderItem(1, QTableWidgetItem('Progress')) 
        # read only
        self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
        # single row selection
        self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
        # signal 
        self.table.itemSelectionChanged.connect(self.tableChanged)
        self.table.itemDoubleClicked.connect(self.tableDbClicked)
        box.addWidget(self.table)
        
        hbox = QHBoxLayout()
        btnAdd = QPushButton('Add List')
        btnDel = QPushButton('Del List')
        btnAdd.clicked.connect(self.addList)
        btnDel.clicked.connect(self.delList)
        hbox.addWidget(btnAdd)
        hbox.addWidget(btnDel)   
        
        box.addLayout(hbox)
        gb.setLayout(box)        
        
        # 2.Play Control
        box = QHBoxLayout()
        gb = QGroupBox('Play Control')
        vbox.addWidget(gb)

        text = ['◀◀', '▶', '⏸', '▶▶', '■']
        grp = QButtonGroup(self)
        for i in range(len(text)):
            btn = QPushButton(text[i], self)            
            grp.addButton(btn, i)
            box.addWidget(btn)
        grp.buttonClicked[int].connect(self.btnClicked)

        # Volume
        self.slider = QSlider(Qt.Horizontal, self)
        self.slider.setRange(0,100)
        self.slider.setValue(50)
        self.slider.valueChanged[int].connect(self.volumeChanged)
        box.addWidget(self.slider)        
        gb.setLayout(box)

        # 3.Play Option
        box = QHBoxLayout()
        gb = QGroupBox('Play Option')
        vbox.addWidget(gb)       

        str = ['current item once', 'current item in loop', 'sequential', 'loop', 'random']
        grp = QButtonGroup(self)
        for i in range(len(str)):
            btn = QRadioButton(str[i], self)
            if i==QMediaPlaylist.Sequential:
                btn.setChecked(True)
            grp.addButton(btn, i)  
            box.addWidget(btn)        
        
        grp.buttonClicked[int].connect(self.radClicked)
            
        gb.setLayout(box)               
        
        self.setLayout(vbox)
        self.show()

    def tableChanged(self):
        self.selectedList.clear()        
        for item in self.table.selectedIndexes():
            self.selectedList.append(item.row())
        
        self.selectedList = list(set(self.selectedList))
        
        if self.table.rowCount()!=0 and len(self.selectedList) == 0:
            self.selectedList.append(0)
        #print(self.selectedList)

    def addList(self):
        files = QFileDialog.getOpenFileNames(self
                                             , 'Select one or more files to open'
                                             , ''
                                             , 'Sound (*.mp3 *.wav *.ogg *.flac *.wma)')        
        cnt = len(files[0])       
        row = self.table.rowCount()        
        self.table.setRowCount(row + cnt)
        for i in range(row, row+cnt):
            self.table.setItem(i,0, QTableWidgetItem(files[0][i-row]))
            pbar = QProgressBar(self.table)
            pbar.setAlignment(Qt.AlignCenter)            
            self.table.setCellWidget(i,1, pbar)
            
        self.createPlaylist()       

    def delList(self):        
        row = self.table.rowCount()      

        index = []       
        for item in self.table.selectedIndexes():
            index.append(item.row())        
        
        index = list(set(index))        
        index.reverse()        
        for i in index:
            self.table.removeRow(i)
        
        self.createPlaylist()       

    def btnClicked(self, id):
        
        if id==0: #◀◀
            self.player.prev()
        elif id==1: #▶
            if self.table.rowCount()>0:
                self.player.play(self.playlist, self.selectedList[0], self.playOption)
        elif id==2: #⏸
            self.player.pause()
        elif id==3: #▶▶
            self.player.next()
        else: #■
            self.player.stop()

    def tableDbClicked(self, e):
        self.player.play(self.playlist, self.selectedList[0], self.playOption)

    def volumeChanged(self):
        self.player.upateVolume(self.slider.value())

    def radClicked(self, id):
        self.playOption = id
        self.player.updatePlayMode(id)

    def paintEvent(self, e):
        self.table.setColumnWidth(0, self.table.width()*0.7)
        self.table.setColumnWidth(1, self.table.width()*0.2)

    def createPlaylist(self):
        self.playlist.clear()
        for i in range(self.table.rowCount()):
            self.playlist.append(self.table.item(i,0).text())

        #print(self.playlist)

    def updateMediaChanged(self, index):
        if index>=0:
            self.table.selectRow(index)            

    def updateDurationChanged(self, index, msec):        
        #print('index:',index, 'duration:', msec)
        self.pbar = self.table.cellWidget(index, 1)
        if self.pbar:
            self.pbar.setRange(0, msec)       

    def updatePositionChanged(self, index, msec):
        #print('index:',index, 'position:', msec)
        self.pbar = self.table.cellWidget(index, 1)
        if self.pbar:
            self.pbar.setValue(msec)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = CWidget()
    sys.exit(app.exec_())

main.py 파일은 윈도우 창 생성을 담당하는 코드입니다.

대부분의 코드는 CWidget 이라는 QWidget으로부터 상속받은 클래스로 구성되어 있습니다.

코드가 조금 길어보이는데, GUI를 위해 버튼이나, 테이블, 그룹박스등을 생성하는 코드가 반정도 차지하므로 어려운 코드는 아닙니다.

Qt의 Designer로 GUI(Graphic User Interface) 컨트롤을 배치하면, 코드의 양은 줄겠지만, 개인적으로 직접 코드를 이용 컨트롤, 윈도우를 생성하는 방법을 선호합니다.

9번 라인 CWidget 클래스의 생성자 함수부터 살펴보겠습니다.

실제 음악 재생과 관련된 player.py 파일의 CPlayer 클래스, 및 음원 리스트를 저장하기 위한 리스트를 멤버 변수로 선언합니다.

그리고 initUI() 함수를 호출해 GUI를 구성합니다.

19번 라인 initUI() 함수는 각종 컨트롤을 배치하여 윈도우 창을 구성하는 함수입니다.

24~49번 라인은 QGroupbox, QTableWidget, QPushButton 등을 사용해 아래 Player List 창을 구성합니다.

[Play List 구성]

Play List 구성 절차

1. 그룹박스 및 레이아웃 박스 생성 연결

2. 음악 리스트 (QTableWidget) 생성 (읽기전용, 한줄 선택)

3. 음악 리스트 시그널, 슬롯 연결 (선택항목 변경, 더블 클릭)

4. 버튼 (음악 리스트 추가, 삭제) 생성 및 시그널 슬롯 연결


52~70번 라인은 QGroupBox, QPushButton, QSlider 등을 이용해 아래 Play Control 창을 생성합니다.

[Play Control 구성]

Play Control 구성 절차

1. 그룹박스 및 레이아웃 박스 생성 연결

2. 푸시버튼 생성 후 버튼 그룹으로 묶음 (버튼 마다 id 부여)

3. 버튼 그룹 클릭시 시그널, 슬롯 처리 (슬롯 호출시 버튼 id 전달)

4. 볼륨 슬라이더 생성 및 값 변경시 시그널, 슬롯 처리

일반적으로 버튼을 여러 개 만드는 경우, 해당 버튼이 눌러졌을때 수행되는 동작을 정의하기위해 버튼마다 클릭시 호출되는 함수를 따로 생성하면 코드가 지저분해지는 경향이 있습니다.

왜냐하면, 버튼의 수만큼 함수가 만들어지기 때문입니다.

이런 경우, 버튼들을 QButtonGroup 클래스를 이용, 그룹으로 묶어 id를 부여해 관리하면 해당 버튼이 눌러진 경우 하나의 함수로 버튼의 id를 체크해 어떤 버튼이 눌러졌는지 처리할 수 있습니다.

MS의 C++ GUI 라이브러리인 MFC는 ON_CONTROL_RANGE() 매크로 함수를 이용해 버튼 시작 ID와 끝 ID를 이용해 처리하는 반면, Qt는 좀 더 사용하기 편리한 기능을 제공합니다.

73~88번 라인은 QGroupbox, GRadioButton 클래스를 이용 Play Option 창을 구성합니다.

[Play Option 구성]

Play Option 구성 절차

1. 그룹박스 및 레이아웃 박스 생성 연결

2. 라디오 버튼 생성 후 버튼 그룹으로 묶기 (Default는 순차재생으로)

3. 버튼 그룹 클릭시, 시그널, 슬롯 처리 (슬롯 호출시 버튼 id 전달)


이제 모든 컨트롤이 vbox(수직레이아웃 박스)에 배치되었습니다.

마지막으로 self.setLayout(vbox) 코드를 이용 윈도우에 QVBoxLayout을 배치하면 끝입니다.

+
+
=
[GUI 완성 모습]

Qt의 QLayoutBox 클래스는 컨트롤 배치를 쉽게하고, 윈도우 크기 변경시 자동으로 Resizing 되는 편의성을 제공해 줍니다.

MFC에도 이런 클래스가 하나 있으면 얼마나 행복할까 생각해 봅니다...

여기까지가 main.py 파일의 CWidget 클래스 생성자 호출 부터 이어지는 initUI() 함수의 설명이었습니다.

이제부터 코드 설명은 코드 라인 순서가 아닌, 실제 플레이어를 동작하는 행위 (이벤트 처리)를 기준으로 코드와 연계해 설명드리겠습니다.

즉, 음악 파일을 추가하고 음악 리스트의 파일을 재생, 정지하는 행위를 하는 사용자 관점의 순서로 코드를 분석해 보겠습니다.

[사용자 관점 코드 분석]

사용자는 먼저 음원 파일을 추가하려 하겠죠.

1. Add List 버튼 클릭

104번 라인의 addList() 슬롯 함수가 호출 됩니다.

QFileDialog 로 파일 열기 창을 생성해, 파일을 하나 또는 다수를 선택합니다.

[파일 열기 창]

불러들인 음원 파일의 수를 구하고 반복문을 이용해 QTableWidget에 추가합니다.

[음원 파일 추가]

주의할 점은 음원파일이 있던 상태에서 다시 추가한 경우를 생각해야 합니다. 즉 음원 파일을 추가한 후 더 추가하는 경우이죠.

따라서 QTableWidget에 음원파일을 추가할 때,  항상 0번 행부터 추가하는 것이 아님을 잘 생각해 처리해야 합니다.


2. Del List 버튼 클릭

다음은 음원 리스트에서 하나, 또는 다수의 음원을 삭제하는 경우입니다.

120번 라인의 delList() 함수가 호출됩니다.

현재 음원 리스트의 개수를 구한 후 삭제하려는 음원 파일의 인덱스 번호를 수집해 리스트에 저장합니다.

예를 들어 [0,1,2] 번 음원을 리스트에서 삭제한다면 반대로 [2,1,0] 번의 순서대로 반복하며 QTableWidget에서 해당 행을 삭제해야 합니다.

왜냐하면 0번 리스트의 음원이 먼저 삭제되면 뒷 행 번호인 1번의 음원이 바로 0번으로 바뀌기 때문에 제대로 삭제되지 않는 문제가 발생합니다.

따라서 QTableWidget 음원리스트의 아래에서부터 삭제를 진행합니다.

즉 선택된 QTableWidget의 음원을 삭제하기 위해, 모든 선택된 행의 번호를 파악하고 순서를 내림차순 정렬아래쪽부터 삭제를 진행해야 합니다.


3. Play, Stop 등 재생 버튼 클릭

이제 음원리스트에 재생목록이 저장되어 있으니 음악을 들어 보겠습니다.

재생, 정지, 다음, 이전, 일시정지 버튼을 누른 경우입니다.

134번 라인의 btnClicked(id) 함수가 호출됩니다.

각 재생관련 버튼을 누르면 발생되는 함수이며, 전달인자로 들어오는 id의 구분은 아래와 같습니다.

뒤로 버튼(◀◀, id 0)
재생 버튼(▶, id 1)
일시정지 버튼(⏸, id 2)
앞으로 버튼(▶▶, id 3)
정지 버튼(■, id 4)

이젠 player.py 파일의 코드를 살펴볼 시점입니다.

QMediaPlayer, QMediaPlayList 등의 클래스를 이용해 실제 음원 파일을 재생하는 역할을 담당합니다.

코드를 한번 살펴보겠습니다.

from PyQt5.QtMultimedia import QMediaPlaylist, QMediaContent, QMediaPlayer
from PyQt5.QtCore import Qt, QUrl

class CPlayer:

    def __init__(self, parent):
        # 윈도우 객체
        self.parent = parent

        self.player = QMediaPlayer()
        self.player.currentMediaChanged.connect(self.mediaChanged)
        self.player.durationChanged.connect(self.durationChanged)
        self.player.positionChanged.connect(self.positionChanged)
        
        self.playlist = QMediaPlaylist()

    def play(self, playlists, startRow=0, option=QMediaPlaylist.Sequential):       
        if self.player.state() == QMediaPlayer.PausedState:
            self.player.play()
        else:              
            self.createPlaylist(playlists, startRow, option)            
            self.player.setPlaylist(self.playlist)
            self.playlist.setCurrentIndex(startRow)
            self.player.play()          
        
    def pause(self):
        self.player.pause()         

    def stop(self):
        self.player.stop()

    def prev(self):        
        self.playlist.previous()     

    def next(self):
        self.playlist.next()

    def createPlaylist(self, playlists, startRow=0, option=QMediaPlaylist.Sequential):        
        self.playlist.clear()      

        for path in playlists:
            url = QUrl.fromLocalFile(path)
            self.playlist.addMedia(QMediaContent(url))

        self.playlist.setPlaybackMode(option)

    def updatePlayMode(self, option):
        self.playlist.setPlaybackMode(option)

    def upateVolume(self, vol):
        self.player.setVolume(vol)

    def mediaChanged(self, e):
        self.parent.updateMediaChanged(self.playlist.currentIndex())       

    def durationChanged(self, msec):
        if msec>0:
            self.parent.updateDurationChanged(self.playlist.currentIndex(), msec)

    def positionChanged(self, msec):
        if msec>0:
            self.parent.updatePositionChanged(self.playlist.currentIndex(), msec)

CPlayer 클래스가 해당파일의 전부입니다.

main.py 의 CWidget 클래스 멤버변수인 self.player가 바로 이 클래스(CPlayer)의 객체 입니다.

이제 재생 버튼을 누르면 CPlayer 클래스의 play() 함수가 호출됩니다.

QMediaPlayList(음원 목록)를 생성하고, QMediaPlayer에 음원 목록을 등록한 후 소리를 재생하는 방식입니다.

주의해야 할 부분은 소리 재생 옵션 (한곡만, 반복재생 등)을 변경하면 호출되는 47번 라인  updatePlayerMode() 함수를 통해 QMediaPlayList의 PlaybackMode를 변경해야 하는 사실입니다.

음악파일을 재생하는 것은 QMediaPlayList를 통해 만든 음원리스트를 QMediaPlayer 에 등록하고, QMediaPlayer 클래스가 제공하는 play, pause, stop 함수를 이용해 쉽게 구현이 가능합니다.


다음으로 고민해볼 문제는 현재 곡을 재생하고 난 후 다음 곡이 재생될때 이를 감지해 화면에서 다음곡으로 선택 포커스를 넘기는 문제입니다.


위 그림에서 1번 음원 재생이 끝나면 2번 행으로 QTableWidget의 포커스를 이동해야 합니다.

11번 라인 QMediaPlayer 클래스의 currentMediaChanged() 시그널을 이용하면 가능합니다.

여기서 잠시 PyQt 클래스 도움말 활용법에 대한 팁을 소개하고자 합니다.

Qt는 C++로 만들어져 있는 클래스 모음입니다. 이를 Python에서 사용하기위해 영국의 Riverbank Computing 이라는 회사에서 Qt를 Python Binding으로 만든 것이 PyQt 입니다.

아래는 Qt 제작사 도움말이며, 저는 개인적으로 원제작사의 C++ 코드로 작성된 클래스 도움말을 보며 PyQt 코드 작성에 참조하고 있습니다.
Qt 문서에 아래와 같이 C++ 코드로 설명 되어 있습니다.

참조로 C++ 함수 선언은 리턴타입 + 함수명 + 전달인자() 로 구성됩니다.

[signal] void QMediaPlayer::currentMediaChanged(const QMediaContent &media)
Signals that the current playing content has been changed to media.


다음은 음원 파일의 재생시간을 알아내는 방법입니다.

12번 라인 durationChanged() 시그널을 이용합니다.

[signal] void QMediaPlayer::durationChanged(qint64 duration)
Signal the duration of the content has changed to duration, expressed in milliseconds.


마지막으로 현재 음원이 어디쯤 재생되고 있는지 알아야 합니다.


어디쯤인지 알아야 QProgressBar 로 진행률을 표시할 수 있겠죠.

13번 라인 positionChanged() 시그널을 이용합니다.

[signal] void QMediaPlayer::positionChanged(qint64 position)
Signal the position of the content has changed to position, expressed in milliseconds.

요약해 보자면 currentMediaChanged를 이용해 다음곡의 포커스를 이동하고, durationChanged를 이용 재생시간을 구해 QProgressBar의 시작, 끝 값을 설정합니다.

단위는 밀리초(1/1000) 단위로 얻습니다.

다음으로 positionChanged를 이용해 현재 재생시간(위치)을 알아내어 QProgressBar의 serValue() 함수를 통해 값을 변경하면 됩니다.


다 만들고 사용하다보니, "단축키를 추가하면 좋을 것 같은데" 라는 아쉬움이 남습니다.

이상 설명을 마칩니다.

실행파일 링크 : 음악플레이어

  • 개발 환경
  1. 운영체제 : MS Windows 10 Pro
  2. 개발 언어 : Python 3.7 (32bit), PyQt5 (5.11.3)
  3. 개발 도구 : MS Visual Studio 2017 Pro

댓글

  1. Main.py와 player.py를 복붙해서 실행했는데요 mp3파일 불러오는거 까지 됐는데 재생 버튼을 눌러도 실행이 안됩니다 왜그럴까요?? exe파일로 만들어야 실행이 되나요??

    답글삭제
    답글
    1. 실행까지 된다면 PYQT는 잘 설치되어 있다는 뜻이고, 혹시 코덱이 mp3, ogg 같은 설치되어 있지 않은 것 아닌가 싶은데요.

      exe와는 무관합니다.

      재생버튼을 눌렀을때 콘솔에 오류 메시지가 뜬다면 원인을 찾기 쉬울것 같습니다.

      삭제
    2. tkinter로 mp3플레이어 만들었을때는 재생이 됐었는데 그거랑은 상관 없나요?? 코덱을 따로 설치 해야될까요

      삭제
    3. defaultServiceProvider::requestService(): no service found for - "org.qt-project.qt.mediaplayer" 이 오류가 뜹니다

      삭제
    4. 음. 흔히 보는 오류가 아니라 검색해보니 혹시 리눅스 환경이라면, 아래 링크와 같은 플러그인 설치가 필요하다고 합니다.

      sudo apt-get install libqt5multimedia5-plugins

      주로 리눅스에서 Qt Multimedia 관련 플러그인이 없어 생기는 오류로 판단이 되고, 아래 글을 참조해 보시면 좋을 것 같습니다.

      https://stackoverflow.com/questions/47524552/qt-no-service-found-for-org-qt-project-qt-mediaplayer

      삭제
    5. mp4파일 불러와서 동영상 출력하게 할려는데 혹시 방법 아시나요??

      삭제
    6. 이 예제와 거의 대부분이 동일한 절차입니다.

      다만 비디오를 출력할 창(위젯)이 필요하므로, QMediaPlayer의 메서드인 setVideoOutput(출력위젯) 함수의 전달인자로 출력위젯만 설정하면 비디오도 재생이 가능합니다.

      삭제
    7. 학원 블로그 검색창에 "동영상"을 검색하면 파이썬 동영상 플레이어 예제가 나옵니다. 참조바랍니다.

      삭제
  2. 질문하나 드립니다. 다음 음악으로 넘어갈때 몇초간의 인터벌을 넣고 싶습니다. 어떻게 해야 되나요?

    답글삭제
    답글
    1. QMediaPlaylist 클래스의 기본 재생모드(PlaybackMode) 는 Sequential(순차) 입니다.

      setPlaybackMode() 함수를 이용해 CurrentItemOnce(한번만) 로 변경한 후, 다음으로 넘어가는 동작을 프로그래머가 직접 쓰레드를 생성해 수동처리(지연, 다음곡 재생 등)해 주면 됩니다.

      삭제
  3. 혹시 QlistWidget 에서는 포커스 이동 테이블이랑 동일 하게 하면 되나여?.
    가지고 오는 포커스에 row 값에 +1 만 했더니 한번은 다음으로 이동 되나.. 2번째부터는 첫번째 이동한 곳에 음성만 출력이 되네여..

    답글삭제
    답글
    1. QListWidget과 무관하게, 예제코드에서 테이블 위젯 포커스 이동에 따른 QMediaPlaylist의 현재곡 변경은 처리해두지 않았습니다.

      만약 이 기능을 구현한다면 QListWidget의 currentRowChanged() 시그널을 재정의하고, 이때 변경된 인덱스를 이용해 QMediaPlaylist의 setCurrentIndex(바뀐 인덱스)를 변경하면 됩니다.

      삭제
  4. main 5번째 줄에서 오류가 납니다
    Qt가 정의되어있지 않다고 하는데 어떤 문제일까요?

    답글삭제
    답글
    1. 4k 모니터를 위한 설정이며, 4k모니터가 아니라면 없어도 무관합니다.
      QtCore모듈에서 Qt 클래스를 불러와야 하는데 해당 코드가 없는 것 같습니다.

      바로 위줄에 아래 코드를 추가해보기 바랍니다.
      from PyQt5.QtCore import Qt

      삭제
    2. 답변 감사합니다 그 이슈는 해결 되었으나 QtMultimedia를 임포트하는 과정에서 오류가 납니다. install목록에도 없어서 난감하네요 저는 현재 파이참을 사용하고 있습니다

      삭제
    3. QtMultimedia 모듈을 임포트 하는데 문제가 있다는 것은 PyQt5가 설치되어 있지 않거나, 또는 경로문제일수도 있습니다.

      PyQt5는 파이썬의 기본 패키지가 아니므로 따로 설치해야만 사용가능합니다. (아나콘다를 사용하면 기본설치)

      미 설치된 경우, 파이참의 Settting에서 해당 프로젝트 Python Interpreter에 '+' 아이콘을 선택하면 패키지를 설치 가능합니다.

      삭제
    4. PyQt5의 다른 것들은 전부 되는데 QtMultmedia만 임포트가 안되고있습니다 ㅠㅠ

      삭제
  5. 안녕하세요 ! addList 부분에서 음악 파일들을 QFileDialog를 이용 해 불러오는 게 아니라 제가 미리 지정해 줄 수는 없을까요?

    답글삭제
    답글
    1. 정해진 곡만 재생 가능하므로 실용성이 없어 보이지만, 아래와 같이 진행하면 됩니다.

      1. main.py파일 생성자에서 바로 테이블 위젯에 곡을 넣습니다.

      2. player.py파일 생성자에서 QMediaPlaylist에 1번에서 지정한 곡을 동일하게 지정합니다. (테이블 위젯과 미디어플레이리스트 동기화)

      삭제
    2. 테이블 위젯에 추가되는 곡은 사용자에게 보여주기 위함이며, 실제 연주를 위해서는 QMediaPlaylist 객체에 곡이 지정되어야 함을 이해해야 합니다.

      따라서 이 둘은 서로 동기화 되어 있어야 합니다.

      (테이블 위젯에 추가하면 플레이리스트에도 추가, 마찬가지로 곡이 삭제되면 양쪽 다 삭제하는 개념)

      삭제
    3. 아하! 감사합니다! 혹시 위에 댓글에서 언급하신 직접 쓰레드를 생성해 지연시간을 주는 것에 대한 코드는 존재할까요?

      삭제
    4. 저도 곡 사이에 딜레이타임을 주고싶어서요!

      삭제
    5. python의 쓰레드 클래스를 사용해 본 적이 없다면 블로그의 다른 예제 (채팅, 피하기 게임, 포물선 운동 등)을 참조바랍니다.

      쓰레드의 개념을 알고 있다면, 곡이 끝나는 이벤트를 감지해 쓰레드를 생성하고 time.sleep(지연시간) 을 이용해 지연 후 다음곡의 인덱스를 재생하면 됩니다.

      삭제
    6. 안녕하세요 김주원님, ms님이 말씀해주신 내용으로 시도해보려고 하는데(정해진 곡 재생) 처음이다 보니 조금 어렵네요, 좀 더 구체적으로 설명해주실 수 있을실까요?

      삭제
    7. 정해진 음악파일만 재생하고 싶다면,

      1. CWidget 생성자 함수에서 테이블 위젯에 정해진 음악파일이 있는 경로를 추가합니다.
      2. CPlayer 생성자 함수에서 위와 동일한 음악파일 경로를 QMediaPlaylist에 추가하면 끝입니다.

      테이블 위젯의 음원경로 추가하는 방법은 위 예제의 AddList(), CPlayer는 CreatePlaylist() 에 이미 구현되어 있습니다.

      물론 파일 추가,제거 버튼의 삭제와 같은 부수적인 절차도 필요하겠죠.

      삭제
  6. 이제 마무리 단계로 exe 파일로 만들고 싶은데 혹시 만드는 방법에 대해 알 수 있을까요?

    답글삭제
    답글
    1. pyinstaller를 이용해 exe 파일을 만들었는데 QtMultimedia 모듈이 없다는 오류가 콘솔창에 뜨네요ㅠㅠ 그래서 spec파일을 열어 hiddenimports에 PyQt5.QtMultimedia을 추가해도 실행이 안되네요 ㅠㅠ

      삭제
    2. 개발환경 문제로 추측되며, 아나콘다 설치시 Qt의 Multimedia Class관련 버그가 생긴다는 Stack Overflow 글을 본적이 있습니다.

      저는 아나콘다를 쓰지 않아 경험한 적은 없지만 관련 이슈가 있으므로, Python 개발 환경을 수동으로(Python.org에서 다운받아 설치) 구성하길 추천드립니다.

      그리고 python으로 Window 배포시 저도 pyinstaller를 사용하고 있으며, Android, IOS 배포는 pyqtdeploy 참조 바랍니다.

      삭제
    3. 해결되었습니다! 많은 질문이 있었는데 친절하게 답변해 주셔서 감사드립니다!

      삭제
  7. 안녕하세요 모듈 에러 발생하여 질문드립니다.
    ModuleNotFoundError Traceback (most recent call last)
    in
    1 from PyQt5.QtWidgets import *
    2 import sys
    ----> 3 from player import player
    4
    5 QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)

    ModuleNotFoundError: No module named 'player'

    위 에러 확인해보면 player모듈이 없는데 해당 모듈 설치했음에도 동일한 에러가 발생하네요... 다르게 확인해볼 부분이 있을지 문의 드립니다.

    답글삭제
    답글
    1. 1 from PyQt5.QtWidgets import *
      2 import sys
      ----> 3 from player import *
      4
      5 QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)

      line 3이 잘못 작성되어 있어서 다시 답글 납깁니다.

      삭제
    2. player라는 모듈은 위 예제에서 만든 player.py 파일을 뜻하며, 외부 모듈이 아닙니다.

      이 예제는 main.py 파일과 player.py 2개의 파이썬 파일로 나누어 구성되어 있으며 두 파일을 같은 경로에 두고 main.py 파일을 실행하면 player.py 파일을 불러오라는 의미입니다.

      두 파일을 같은 경로에 두고 main.py를 실행하면 player.py를 from 구문으로 불러오고 앱이 실행 됩니다.

      삭제
    3. 답변 감사합니다. 추가로, player.py 실행시

      ModuleNotFoundError Traceback (most recent call last)
      in
      ----> 1 from PyQt5.QtMultimedia import QMediaPlaylist, QMediaContent, QMediaPlayer
      2 from PyQt5.QtCore import Qt, QUrl
      3
      4 class CPlayer:
      5

      ModuleNotFoundError: No module named 'PyQt5.QtMultimedia'

      Pyqt5 multimedia 모듈 에러가 발생하고 있습니다. 쥬피터노트로 실행중이긴한데 문제가 있을까요? Pyqt5설치는 아나콘다로 진행하였습니다. 위 댓글 보면 아나콘다 설치 시 버그가 있다고 하는데 그 문제일까요?

      삭제
    4. 개발환경문제로 추측됩니다.

      정확한 문제는 질문자분의 개발환경을 모르니 답변드리기 어렵지만, 파이썬의 외부 모듈은 파이썬 설치경로/Lib/site-packages/... 에 존재해야 하며 그 경로는 환경변수에 등록되어야 합니다.

      삭제
    5. 답변 감사합니다. 큰 도움 되었습니다!

      삭제
  8. 안녕하세요 오류가 떠서 질문드립니다.
    오류 내용은 This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem. 입니다. 구글링도 해보고 내용처럼 재설치도 해보고 이것저것 해봤는데도 해결이 되지 않아 질문 드립니다.

    답글삭제
    답글
    1. 코드 실행은 IDLE 파이썬 3.9로 했습니다

      삭제
    2. 해결되었습니다
      글 잘 봤습니다 감사합니다

      삭제
  9. 안녕하세요! 프로그램을 만들어 보았습니다!
    프로그램은 잘 돌아가는데 혹시 마우스나 키보드를 사용하기 어려울 때 재생, 일시정지 버튼 등을 클릭하기 위해 외부 리모콘 기기와 연동해 보고 싶은데 혹시 그런 방법은 없을까요?

    답글삭제
    답글
    1. 코드로 구현해야할 문제가 아니라 그런 제품을 구입해 PC에 연결하면 간단히 해결되지 않을까요. 마치 무선 마우스 같은 느낌으로.

      삭제
    2. 구입 해 연결해 봤지만 groove와 window media player같은 기본앱으로 설정 된 어플에만 작동하더라구요... 혹시 이걸 변경할 수 있는 방법은 없을까요?ㅠㅠ

      삭제
    3. 기본앱 여부와 상관없이 앱의 버튼(재생, 정지 등)에 단축키를 추가하면 될 것 같습니다.

      삭제
  10. 음악을 플레이했을때, 2022-06-12 18:23:21.725 Python[52692:7907710] -[NSNull length]: unrecognized selector sent to instance 0x7ff85bd9a0c0
    2022-06-12 18:23:21.725 Python[52692:7907710] Exception calling directoryDidChange: on delegate exception=-[NSNull length]: ......와 같은 에러가 나는데 왜이러는 건가요?

    답글삭제
    답글
    1. 지원아 맥에서 해본거면 거기서는 테스트 안해봐서 확인해봐야 할 것 같아.

      오류메시지만 봐서는 파일 경로 오류 같네.

      삭제

댓글 쓰기

이 블로그의 인기 게시물

Qt Designer 설치하기

C++ 예제 (소켓 서버, 이미지, 파일전송)