Building a PyQt5 Music Player with Audio File Image Extraction

Implementation of a PyQt5 Music Player with Pygame and Audio Metadata Processing

# Core Library Imports
import sys
import os
import pygame
import time
import random
import threading

# GUI and Multimedia Libraries
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QListWidget, QListWidgetItem
from PyQt5.QtCore import QTimer, Qt
from PyQt5.QtGui import QPixmap
import qtawesome

# Audio and Image Processing
from mutagen import File
from PIL import Image, ImageDraw, ImageFilter
import requests

Audio Playback and List Management

Handlnig double-click events on playlist items:

def handle_double_click(self, playlist_widget):
    """Process double-click events on playlist items to play selected audio."""
    global current_track_index
    selected_item = QListWidgetItem(playlist_widget.currentItem())
    track_name = selected_item.text()
    current_track_index = playlist_widget.currentRow()
    
    # Extract and display filename
    full_path = audio_file_paths[current_track_index]
    directory, filename_with_ext = os.path.split(full_path)
    filename, extension = os.path.splitext(filename_with_ext)
    self.track_display_label.setText(filename)
    
    self.initiate_playback(current_track_index)

def initiate_playback(self, track_index):
    """Load and play the selected audio file."""
    global playback_paused
    self.extract_album_art(track_index)
    self.play_pause_button.setIcon(qtawesome.icon('fa.pause', color='#3FC89C', font=18))
    playback_paused = False
    
    try:
        pygame.mixer.stop()
    except:
        pass
    
    pygame.mixer.init()
    self.progress_timer = QTimer()
    self.progress_timer.start(500)
    
    audio_path = audio_file_paths[track_index]
    pygame.mixer.music.load(audio_path)
    pygame.mixer.music.play()

Volume Control Implementation

class VolumeManager:
    """Manages audio volume adjustments."""
    def __init__(self):
        self.current_volume = 0.5
    
    def increase_volume(self):
        """Increase volume by 10%."""
        self.current_volume += 0.1
        if self.current_volume > 1.0:
            self.current_volume = 1.0
        pygame.mixer.music.set_volume(self.current_volume)
        
    def decrease_volume(self):
        """Decrease volume by 10%."""
        self.current_volume -= 0.1
        if self.current_volume < 0.0:
            self.current_volume = 0.0
        pygame.mixer.music.set_volume(self.current_volume)

Playback State Control

def toggle_playback(self):
    """Toggle between play and pause states."""
    global playback_paused
    if playback_paused:
        try:
            pygame.mixer.music.unpause()
        except:
            pass
        self.play_pause_button.setIcon(qtawesome.icon('fa.pause', color='#3FC89C', font=18))
        playback_paused = False
    else:
        try:
            pygame.mixer.music.pause()
        except:
            pass
        self.play_pause_button.setIcon(qtawesome.icon('fa.play', color='#F76677', font=18))
        playback_paused = True

Directory Browsing for Audio Files

def browse_audio_directory(self):
    """Open directory dialog and load supported audio files."""
    global audio_file_paths, current_track_index, base_directory
    
    current_track_index = 0
    directory_path = QFileDialog.getExistingDirectory(None, "Select Music Folder", "")
    
    if not directory_path:
        return
    
    self.playlist_widget.clear()
    base_directory = directory_path + '/'
    
    # Collect audio files
    supported_extensions = ('.mp3', '.flac', '.wma')
    audio_files = []
    for filename in os.listdir(directory_path):
        if filename.lower().endswith(supported_extensions):
            audio_files.append(filename)
    
    audio_file_paths = [base_directory + file for file in audio_files]
    
    # Populate playlist
    for idx, audio_file in enumerate(audio_files):
        self.playlist_widget.addItem(audio_file)
        self.playlist_widget.item(idx).setForeground(QtCore.Qt.white)

Custom Window Draggnig

def mousePressEvent(self, event):
    """Handle mouse press for window dragging."""
    self.drag_active = True
    self.drag_start_position = event.globalPos() - self.pos()
    event.accept()
    
def mouseMoveEvent(self, event):
    """Handle window movement during drag."""
    if self.drag_active:
        self.move(event.globalPos() - self.drag_start_position)
        event.accept()
    
def mouseReleaseEvent(self, event):
    """Clean up after drag operation."""
    self.drag_active = False

Audio Metadata Image Extraction

def extract_album_art(self, track_index):
    """Extract and display album art from audio files."""
    try:
        audio_metadata = File(audio_file_paths[track_index])
        album_art_data = audio_metadata.tags['APIC:'].data
        
        # Save raw image data
        with open('temp_cover.png', 'wb') as image_file:
            image_file.write(album_art_data)
        
        # Process and display image
        source_path = './temp_cover.png'
        processed_path = './processed_cover.png'
        self.create_circular_image(source_path, processed_path)
        
        cover_pixmap = QtGui.QPixmap(processed_path)
        scaled_cover = cover_pixmap.scaled(300, 300, QtCore.Qt.KeepAspectRatio)
        self.cover_display_label.setPixmap(scaled_cover)
        
    except KeyError:
        # Use default image if no album art found
        if os.path.exists("default_cover.png"):
            default_pixmap = QtGui.QPixmap('./default_cover.png')
            scaled_default = default_pixmap.scaled(300, 300, QtCore.Qt.KeepAspectRatio)
            self.cover_display_label.setPixmap(scaled_default)

Image Processing Utilities

def create_max_square_crop(image):
    """Crop image to largest possible square."""
    return center_crop(image, min(image.size), min(image.size))

def center_crop(image, target_width, target_height):
    """Crop image from center to specified dimensions."""
    image_width, image_height = image.size
    return image.crop(((image_width - target_width) // 2,
                       (image_height - target_height) // 2,
                       (image_width + target_width) // 2,
                       (image_height + target_height) // 2))

def create_circular_mask(image, blur_radius, offset=0):
    """Apply circular mask with transparency."""
    offset = blur_radius * 2 + offset
    mask = Image.new("L", image.size, 0)
    draw = ImageDraw.Draw(mask)
    draw.ellipse((offset, offset, 
                  image.size[0] - offset, 
                  image.size[1] - offset), fill=255)
    
    blurred_mask = mask.filter(ImageFilter.GaussianBlur(blur_radius))
    result_image = image.copy()
    result_image.putalpha(blurred_mask)
    return result_image

def process_cover_image(source_path, output_path):
    """Process cover image into circular thumbnail."""
    original_image = Image.open(source_path)
    target_size = 600
    
    # Create square crop
    square_crop = create_max_square_crop(original_image)
    resized_image = square_crop.resize((target_size, target_size), Image.LANCZOS)
    
    # Apply circular mask
    circular_image = create_circular_mask(resized_image, 0)
    circular_image.save(output_path)
    os.remove(source_path)

Main Application Structure

class MusicPlayerApp(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.initialize_ui()
        self.setup_ui_elements()
        
    def initialize_ui(self):
        """Set up main window properties."""
        self.setFixedSize(960, 700)
        self.setWindowOpacity(0.9)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
        
        # Initialize playback state
        self.playback_mode = 'sequential'
        self.current_volume = 0.5
        self.is_paused = False
        self.audio_file_paths = []
        self.current_track_index = 0
        
    def setup_ui_elements(self):
        """Create and configure UI components."""
        # Main layout containers
        self.main_widget = QtWidgets.QWidget()
        self.main_layout = QtWidgets.QGridLayout()
        self.main_widget.setLayout(self.main_layout)
        
        # Playlist display
        self.playlist_widget = QListWidget(self)
        self.playlist_widget.doubleClicked.connect(
            lambda: self.handle_double_click(self.playlist_widget))
        
        # Control buttons
        self.play_pause_button = QtWidgets.QPushButton(
            qtawesome.icon('fa.pause', color='#3FC89C', font=18), "")
        self.play_pause_button.clicked.connect(self.toggle_playback)
        
        # Volume controls
        self.volume_up_button = QtWidgets.QPushButton(
            qtawesome.icon('fa.volume-up', color='#3FC89C', font=18), "")
        self.volume_up_button.clicked.connect(self.increase_volume)
        
        self.volume_down_button = QtWidgets.QPushButton(
            qtawesome.icon('fa.volume-down', color='#3FC89C', font=18), "")
        self.volume_down_button.clicked.connect(self.decrease_volume)
        
        # Track information display
        self.track_display_label = QtWidgets.QLabel("No track selected")
        self.cover_display_label = QtWidgets.QLabel()

Tags: PyQt5 pygame Music Player Python GUI Audio Processing

Posted on Sun, 11 Oct 2026 16:25:11 +0000 by GirishR