דילוג לתוכן
  • חוקי הפורום
  • פופולרי
  • לא נפתר
  • משתמשים
  • חיפוש גוגל בפורום
  • צור קשר
עיצובים
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • ברירת מחדל (ללא עיצוב (ברירת מחדל))
  • ללא עיצוב (ברירת מחדל)
כיווץ
מתמחים טופ
  1. דף הבית
  2. קטגוריות בהרצה
  3. תכנות
  4. הצעות לפיתוח
  5. תוכנות בביצוע
  6. שיתוף | נגן הקלדה עם מספר פיצ'רים נחמדים...

שיתוף | נגן הקלדה עם מספר פיצ'רים נחמדים...

מתוזמן נעוץ נעול הועבר תוכנות בביצוע
5 פוסטים 2 כותבים 201 צפיות 4 עוקבים
  • מהישן לחדש
  • מהחדש לישן
  • הכי הרבה הצבעות
תגובה
  • תגובה כנושא
התחברו כדי לפרסם תגובה
נושא זה נמחק. רק משתמשים עם הרשאות מתאימות יוכלו לצפות בו.
  • 25802 מנותק
    25802 מנותק
    2580
    מדריכים
    כתב נערך לאחרונה על ידי 2580
    #1

    ייתכן ויש כבר וגם ככה זה לא דבר כזה נצרך אבל לצורך מסויים יצרתי את זה ביחד עם AI לא השקעתי בזה הרבה... בכל אופן זה התוצאה...

    הנגן מיועד לאנשים שמעוניינים להקליד שיעורים וכדו' וקשה להם להמשיך לעקוב אחרי ההמשך תוך כדי הקלדה...

    קישור למג'יקוד (הקובץ חתום) עד שמישהו ( @kasnik אולי?) יעלה את זה לדרייב...

    עריכה: הועלה לדרייב... (קבוע בע"ה) קרדיט - @kasnik

    https://did.li/qlUrl

    קוד המקור... (אל תכעסו אל הארכיטקטורה...)

    התקנות תלויות למי שמשתמש בקוד מקור

    pip install PyQt6 pynput mutagen
    

    נגן_הקלדה.py

    או הקוד עצמו בספויילר

    import sys
    import os
    from functools import partial
    from pynput import keyboard
    from PyQt6.QtCore import (
        QThread, pyqtSignal, QObject, QUrl, Qt, QTimer, QSize, QSettings,
        QPropertyAnimation, pyqtProperty
    )
    from PyQt6.QtWidgets import (
        QApplication, QMainWindow, QWidget, QPushButton, QSlider, QLabel,
        QVBoxLayout, QHBoxLayout, QFileDialog, QStyle, QDialog,
        QDoubleSpinBox, QDialogButtonBox, QGroupBox, QAbstractSpinBox
    )
    from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput
    from PyQt6.QtGui import QPixmap, QFont, QPainter, QBrush, QColor
    from mutagen.mp3 import MP3
    from mutagen.flac import FLAC
    
    
    # --- ווידג'ט מתג ויזואלי ---
    class SlidingToggleSwitch(QWidget):
        """מחלקה המייצרת מתג החלקה ויזואלי (Toggle Switch)."""
        toggled = pyqtSignal(bool)
    
        def __init__(self, parent=None):
            super().__init__(parent)
            self._width = 40
            self._height = 20
            self.setFixedSize(self._width, self._height)
            self.setCursor(Qt.CursorShape.PointingHandCursor)
    
            self._padding = 2
            self._circle_diameter = self._height - self._padding * 2
            self._x_off = self._padding
            self._x_on = self._width - self._circle_diameter - self._padding
    
            self._checked = False
            self._circle_pos = self._x_off
    
            # הגדרת אנימציה להזזת העיגול
            self.animation = QPropertyAnimation(self, b"circle_pos")
            self.animation.setDuration(150)
    
        def mousePressEvent(self, event):
            self._checked = not self._checked
            self.toggled.emit(self._checked)
            self.animate()
            super().mousePressEvent(event)
    
        def paintEvent(self, event):
            """מצייר את המתג בהתאם למצבו (דולק/כבוי)."""
            painter = QPainter(self)
            painter.setRenderHint(QPainter.RenderHint.Antialiasing)
            bg_color = QColor("#3498DB" if self._checked else "#BDC3C7")
            painter.setBrush(QBrush(bg_color))
            painter.setPen(Qt.PenStyle.NoPen)
            painter.drawRoundedRect(0, 0, self._width, self._height, self._height / 2, self._height / 2)
            painter.setBrush(QBrush(QColor("#FFFFFF")))
            painter.drawEllipse(int(self._circle_pos), self._padding, self._circle_diameter, self._circle_diameter)
    
        def animate(self):
            end_pos = self._x_on if self._checked else self._x_off
            self.animation.setStartValue(self._circle_pos)
            self.animation.setEndValue(end_pos)
            self.animation.start()
    
        def get_circle_pos(self):
            return self._circle_pos
    
        def set_circle_pos(self, pos):
            self._circle_pos = pos
            self.update()
    
        # מאפיין המאפשר אנימציה חלקה של מיקום העיגול
        circle_pos = pyqtProperty(float, get_circle_pos, set_circle_pos)
    
        def isChecked(self):
            return self._checked
    
        def setChecked(self, checked):
            if self._checked == checked:
                return
            self._checked = checked
            self._circle_pos = self._x_on if checked else self._x_off
            self.toggled.emit(checked)
            self.update()
    
    # --- עיצוב כהה (Dark Mode) עבור כלל האפליקציה ---
    DARK_STYLESHEET = """
    QWidget {
        background-color: #2b2b2b;
        color: #f0f0f0;
        font-family: Arial, sans-serif;
    }
    QMainWindow {
        background-color: #2b2b2b;
    }
    QPushButton {
        background-color: #555;
        border: 1px solid #666;
        padding: 8px;
        border-radius: 4px;
    }
    QPushButton:hover {
        background-color: #666;
    }
    QPushButton:pressed {
        background-color: #444;
    }
    QPushButton#ControlButton {
        border: none;
        font-size: 18px;
        font-weight: bold;
        background-color: #3c3c3c;
        border-radius: 22px;
    }
    QPushButton#ControlButton:hover {
        background-color: #505050;
    }
    QPushButton#ControlButton:pressed {
        background-color: #2a2a2a;
    }
    QPushButton#ControlButton[active="true"] {
        background-color: #5a9bcf;
        color: white;
    }
    QLabel#AlbumArtLabel {
        border: 2px solid #444;
        border-radius: 5px;
        background-color: #3c3c3c;
    }
    QLabel#TrackInfoLabel {
        font-size: 16px;
        font-weight: bold;
        padding-bottom: 5px;
    }
    QLabel#ArtistAlbumLabel {
        font-size: 12px;
        color: #ccc;
        padding-bottom: 10px;
    }
    QSlider::groove:horizontal {
        border: 1px solid #444;
        height: 8px;
        background: #3c3c3c;
        margin: 2px 0;
        border-radius: 4px;
    }
    QSlider::sub-page:horizontal {
        background: #5a9bcf;
        border: 1px solid #444;
        height: 8px;
        border-radius: 4px;
    }
    QSlider::handle:horizontal {
        background: white;
        border: 1px solid #aaa;
        width: 16px;
        margin: -5px 0;
        border-radius: 8px;
    }
    QDialog {
        background-color: #3c3c3c;
    }
    QGroupBox {
        font-size: 14px;
        font-weight: bold;
        border: 1px solid #444;
        border-radius: 5px;
        margin-top: 10px;
    }
    QGroupBox::title {
        subcontrol-origin: margin;
        subcontrol-position: top center;
        padding: 0 5px;
        background-color: #3c3c3c;
    }
    QDoubleSpinBox {
        background-color: #2b2b2b;
        border: 1px solid #666;
        padding: 5px;
        border-radius: 4px;
        font-size: 14px;
    }
    QDialogButtonBox QPushButton {
        padding: 5px 15px;
    }
    """
    
    # --- חלון דיאלוג להגדרות האפליקציה ---
    class SettingsDialog(QDialog):
        """מנהל את הגדרות המשתמש כמו השהייה בזמן הקלדה ומהירות ניגון."""
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setWindowTitle("הגדרות")
            self.setModal(True)
            self.setMinimumWidth(420)
            self.setLayoutDirection(Qt.LayoutDirection.RightToLeft)
            
            # שימוש ב-QSettings לשמירת הגדרות בין הפעלות
            self.settings = QSettings("MyMusicPlayer", "Settings")
    
            main_layout = QVBoxLayout(self)
            main_layout.setSpacing(15)
            main_layout.setContentsMargins(15, 15, 15, 15)
    
            typing_group = QGroupBox("השהייה בזמן הקלדה")
            typing_layout = QHBoxLayout()
            typing_layout.setContentsMargins(10, 15, 10, 10)
            typing_layout.setSpacing(10)
            
            self.typing_pause_switch = SlidingToggleSwitch()
            self.delay_spinbox = QDoubleSpinBox()
            self.delay_spinbox.setRange(0.5, 10.0)
            self.delay_spinbox.setSingleStep(0.1)
            self.delay_spinbox.setSuffix(" שניות")
            self.delay_spinbox.setAlignment(Qt.AlignmentFlag.AlignCenter)
            self.delay_spinbox.setButtonSymbols(QAbstractSpinBox.ButtonSymbols.NoButtons)
            self.delay_spinbox.setMinimumWidth(80)
    
            self.delay_down_button = QPushButton("-")
            self.delay_down_button.setFixedSize(30, 30)
            self.delay_up_button = QPushButton("+")
            self.delay_up_button.setFixedSize(30, 30)
    
            typing_layout.addWidget(QLabel("הפעלה:"))
            typing_layout.addWidget(self.typing_pause_switch)
            typing_layout.addStretch()
            typing_layout.addWidget(QLabel("זמן:"))
            typing_layout.addWidget(self.delay_spinbox)
            typing_layout.addWidget(self.delay_down_button)
            typing_layout.addWidget(self.delay_up_button)
            typing_group.setLayout(typing_layout)
            main_layout.addWidget(typing_group)
            
            rate_group = QGroupBox("מהירות ניגון")
            rate_container = QWidget()
            rate_layout = QHBoxLayout(rate_container)
            rate_layout.setContentsMargins(10, 15, 10, 10)
            rate_layout.setSpacing(10)
            rate_container.setLayoutDirection(Qt.LayoutDirection.LeftToRight)
    
            self.decrease_rate_button = QPushButton("◀")
            self.decrease_rate_button.setFixedSize(30, 30)
            self.playback_rate_slider = QSlider(Qt.Orientation.Horizontal)
            self.playback_rate_slider.setRange(25, 400) # מייצג 0.25x עד 4.0x
            self.playback_rate_slider.setSingleStep(5)
            self.increase_rate_button = QPushButton("▶")
            self.increase_rate_button.setFixedSize(30, 30)
            self.playback_rate_label = QLabel("1.00 x")
            self.playback_rate_label.setFixedWidth(55)
            self.playback_rate_label.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
    
            rate_layout.addWidget(self.decrease_rate_button)
            rate_layout.addWidget(self.playback_rate_slider)
            rate_layout.addWidget(self.increase_rate_button)
            rate_layout.addWidget(self.playback_rate_label)
            group_main_layout = QVBoxLayout(rate_group)
            group_main_layout.addWidget(rate_container)
            main_layout.addWidget(rate_group)
            
            self.connect_settings_signals()
            
            button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
            button_box.setLayoutDirection(Qt.LayoutDirection.LeftToRight)
            button_box.accepted.connect(self.accept)
            button_box.rejected.connect(self.reject)
            
            main_layout.addStretch(1)
            main_layout.addWidget(button_box)
            
            self.load_settings()
            
        def connect_settings_signals(self):
            self.playback_rate_slider.valueChanged.connect(self.update_rate_label)
            self.decrease_rate_button.clicked.connect(self.decrease_rate)
            self.increase_rate_button.clicked.connect(self.increase_rate)
            self.delay_down_button.clicked.connect(self.decrease_delay)
            self.delay_up_button.clicked.connect(self.increase_delay)
            
        def decrease_delay(self): self.delay_spinbox.stepDown()
        def increase_delay(self): self.delay_spinbox.stepUp()
        def decrease_rate(self): self.playback_rate_slider.setValue(self.playback_rate_slider.value() - 25)
        def increase_rate(self): self.playback_rate_slider.setValue(self.playback_rate_slider.value() + 25)
        def update_rate_label(self, value): self.playback_rate_label.setText(f"{value / 100.0:.2f} x")
            
        def load_settings(self):
            """טוען הגדרות שמורות מהפעלה קודמת."""
            typing_pause_enabled = self.settings.value("typingPauseEnabled", True, type=bool)
            delay_time = self.settings.value("delayTime", 1.5, type=float)
            playback_rate = self.settings.value("playbackRate", 1.0, type=float)
            
            self.typing_pause_switch.setChecked(typing_pause_enabled)
            self.delay_spinbox.setValue(delay_time)
            slider_value = int(playback_rate * 100)
            self.playback_rate_slider.setValue(slider_value)
            self.update_rate_label(slider_value)
    
        def save_settings(self):
            """שומר את ההגדרות הנוכחיות."""
            self.settings.setValue("typingPauseEnabled", self.typing_pause_switch.isChecked())
            self.settings.setValue("delayTime", self.delay_spinbox.value())
            self.settings.setValue("playbackRate", self.playback_rate_slider.value() / 100.0)
    
        def accept(self):
            self.save_settings()
            super().accept()
    
    # --- מאזין גלובלי למקלדת ---
    class GlobalKeyListener(QObject):
        """
        אובייקט המאזין להקשות מקלדת ברמה הגלובלית.
        הוא יועבר ל-Thread נפרד כדי לא להקפיא את ממשק המשתמש.
        """
        keyPressed = pyqtSignal()
        
        def __init__(self):
            super().__init__()
            self.listener = keyboard.Listener(on_press=self.on_press)
    
        def on_press(self, key):
            self.keyPressed.emit()
            
        def start_listening(self):
            self.listener.start()
            self.listener.join()
            
        def stop_listening(self):
            self.listener.stop()
    
    # --- חלון הנגן הראשי ---
    class MusicPlayer(QMainWindow):
        def __init__(self):
            super().__init__()
    
            self.normal_size = QSize(520, 600)
            self.mini_size = QSize(460, 160)
            
            self.setWindowTitle("נגן הקלדה")
            self.setGeometry(100, 100, self.normal_size.width(), self.normal_size.height())
            self.setWindowIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaPlay))
    
            self.settings = QSettings("MyMusicPlayer", "Settings")
            self.player = QMediaPlayer()
            self.audio_output = QAudioOutput()
            self.player.setAudioOutput(self.audio_output)
            
            self.current_file_path = None
            self.is_always_on_top = False
            self.was_playing_before_typing = False
            
            self.typing_pause_enabled = True
            self.delay_time = 1.5
            self.playback_rate = 1.0
            
            self.typing_timer = QTimer(self)
            self.typing_timer.setSingleShot(True)
    
            self.load_player_settings()
            self.update_typing_timer_interval()
            self.apply_playback_rate()
    
            self.init_ui()
            self.connect_signals()
            
            self.setup_global_key_listener()
            
            self.setStyleSheet(DARK_STYLESHEET)
        
        def setup_global_key_listener(self):
            """מגדיר ומפעיל את המאזין הגלובלי למקלדת ב-Thread נפרד."""
            self.key_listener_thread = QThread()
            self.key_listener = GlobalKeyListener()
            
            self.key_listener.moveToThread(self.key_listener_thread)
            self.key_listener.keyPressed.connect(self.handle_global_key_press)
            self.key_listener_thread.started.connect(self.key_listener.start_listening)
            self.key_listener_thread.start()
    
        def handle_global_key_press(self):
            """מטפל בלחיצת מקש גלובלית: משהה את הנגן ומפעיל טיימר לחידוש."""
            if self.typing_pause_enabled:
                if self.player.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
                    self.was_playing_before_typing = True
                    self.player.pause()
                self.typing_timer.start()
    
        def closeEvent(self, event):
            """מבטיח סגירה נקייה של ה-Thread של המאזין בעת יציאה מהאפליקציה."""
            self.key_listener.stop_listening() 
            self.key_listener_thread.quit()    
            self.key_listener_thread.wait()    
            event.accept() 
            
        def load_player_settings(self):
            """טוען הגדרות מה-QSettings ומחיל אותן על הנגן."""
            self.typing_pause_enabled = self.settings.value("typingPauseEnabled", True, type=bool)
            self.delay_time = self.settings.value("delayTime", 1.5, type=float)
            self.playback_rate = self.settings.value("playbackRate", 1.0, type=float)
    
        def update_typing_timer_interval(self):
            self.typing_timer.setInterval(int(self.delay_time * 1000)) # המרה לשניות
    
        def apply_playback_rate(self):
            self.player.setPlaybackRate(self.playback_rate)
            
        def init_ui(self):
            """בונה את כל רכיבי ממשק המשתמש."""
            central_widget = QWidget()
            self.setCentralWidget(central_widget)
            main_layout = QVBoxLayout(central_widget)
    
            # --- שורת כלים עליונה ---
            top_bar_layout = QHBoxLayout()
            self.open_file_button = QPushButton("פתח קובץ")
            self.pin_button = QPushButton("📌")
            self.pin_button.setToolTip("הצמד למעלה (מצב מיני)")
            self.pin_button.setFixedSize(45, 45)
            self.pin_button.setObjectName("ControlButton")
            self.pin_button.setProperty("active", self.is_always_on_top)
            font_pin = self.pin_button.font(); font_pin.setPointSize(20); self.pin_button.setFont(font_pin)
            self.settings_button = QPushButton("⚙️")
            self.settings_button.setToolTip("הגדרות")
            self.settings_button.setFixedSize(45, 45)
            self.settings_button.setObjectName("ControlButton")
            font_settings = self.settings_button.font(); font_settings.setPointSize(22); self.settings_button.setFont(font_settings)
            self.mute_button = QPushButton()
            self.mute_button.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaVolume))
            self.mute_button.setFixedSize(45, 45)
            self.mute_button.setObjectName("ControlButton")
            self.mute_button.setToolTip("השתק / בטל השתקה")
            self.volume_slider = QSlider(Qt.Orientation.Horizontal)
            self.volume_slider.setRange(0, 100)
            self.volume_slider.setValue(70)
            self.audio_output.setVolume(0.7)
            self.volume_slider.setToolTip("עוצמת שמע")
    
            top_bar_layout.addWidget(self.open_file_button)
            top_bar_layout.addStretch()
            top_bar_layout.addWidget(self.mute_button)
            top_bar_layout.addWidget(self.volume_slider)
            top_bar_layout.addStretch()
            top_bar_layout.addWidget(self.pin_button)
            top_bar_layout.addWidget(self.settings_button)
            
            # --- תצוגת מידע ואלבום ---
            self.album_art_label = QLabel("Album Art")
            self.album_art_label.setObjectName("AlbumArtLabel")
            self.album_art_label.setScaledContents(True)
            self.album_art_label.setMinimumSize(300, 300)
            self.album_art_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
            self.track_info_label = QLabel("לא נטען שיר")
            self.track_info_label.setObjectName("TrackInfoLabel")
            self.track_info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
            self.artist_album_label = QLabel("אמן - אלבום")
            self.artist_album_label.setObjectName("ArtistAlbumLabel")
            self.artist_album_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
    
            # --- סליידר התקדמות ---
            progress_layout = QHBoxLayout()
            self.current_time_label = QLabel("00:00")
            self.progress_slider = QSlider(Qt.Orientation.Horizontal)
            self.total_time_label = QLabel("00:00")
            self.progress_slider.setToolTip("התקדמות השיר")
            progress_layout.addWidget(self.current_time_label)
            progress_layout.addWidget(self.progress_slider)
            progress_layout.addWidget(self.total_time_label)
    
            # --- כפתורי שליטה ---
            controls_layout = QHBoxLayout()
            button_size = QSize(45, 45)
            
            self.seek_back_20_button = QPushButton("⏮️")
            self.seek_back_10_button = QPushButton("⏪")
            self.seek_back_5_button = QPushButton("◀️")
            self.stop_button = QPushButton()
            self.play_pause_button = QPushButton()
            self.seek_fwd_5_button = QPushButton("▶️")
            self.seek_fwd_10_button = QPushButton("⏩")
            self.seek_fwd_20_button = QPushButton("⏭️")
            
            self.stop_button.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaStop))
            self.play_pause_button.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaPlay))
            self.play_pause_button.setIconSize(QSize(24, 24))
            self.stop_button.setIconSize(QSize(20, 20))
            
            control_buttons = [
                (self.seek_back_20_button, "הרץ 20 שניות אחורה"), (self.seek_back_10_button, "הרץ 10 שניות אחורה"),
                (self.seek_back_5_button, "הרץ 5 שניות אחורה"), (self.stop_button, "עצור נגינה"),
                (self.play_pause_button, "נגן / השהה"), (self.seek_fwd_5_button, "הרץ 5 שניות קדימה"),
                (self.seek_fwd_10_button, "הרץ 10 שניות קדימה"), (self.seek_fwd_20_button, "הרץ 20 שניות קדימה")
            ]
            
            controls_layout.addStretch()
            for button, tooltip in control_buttons:
                button.setFixedSize(button_size)
                button.setObjectName("ControlButton")
                button.setToolTip(tooltip)
                controls_layout.addWidget(button)
            controls_layout.addStretch()
            
            # --- הרכבת הממשק הראשי ---
            main_layout.addLayout(top_bar_layout)
            main_layout.addStretch(1)
            main_layout.addWidget(self.album_art_label)
            main_layout.addWidget(self.track_info_label)
            main_layout.addWidget(self.artist_album_label)
            main_layout.addLayout(progress_layout)
            main_layout.addLayout(controls_layout)
            main_layout.addStretch(1)
    
        def connect_signals(self):
            """מחבר את כל הסיגנלים של הווידג'טים לפונקציות (סלוטים) המתאימות."""
            self.open_file_button.clicked.connect(self.open_file)
            self.play_pause_button.clicked.connect(self.play_pause)
            self.stop_button.clicked.connect(self.stop_player)
            self.mute_button.clicked.connect(self.toggle_mute)
            self.settings_button.clicked.connect(self.open_settings_dialog)
            self.pin_button.clicked.connect(self.toggle_always_on_top)
    
            seek_map = {
                self.seek_back_20_button: -20, self.seek_back_10_button: -10, self.seek_back_5_button: -5,
                self.seek_fwd_5_button: 5, self.seek_fwd_10_button: 10, self.seek_fwd_20_button: 20
            }
            
            # שימוש ב-partial כדי להעביר ארגומנט (מספר שניות) לפונקציית ה-seek
            for button, seconds in seek_map.items():
                button.clicked.connect(partial(self.seek_by_seconds, seconds))
    
            # חיבור סיגנלים מהנגן עצמו לעדכון הממשק
            self.player.positionChanged.connect(self.update_position)
            self.player.durationChanged.connect(self.update_duration)
            self.player.playbackStateChanged.connect(self.update_play_pause_icon)
            self.player.mediaStatusChanged.connect(self.handle_media_status)
            
            self.progress_slider.sliderMoved.connect(self.set_position)
            self.volume_slider.valueChanged.connect(self.set_volume)
            self.typing_timer.timeout.connect(self.resume_after_typing)
    
        def open_settings_dialog(self):
            """פותח את חלון ההגדרות ומחיל שינויים אם המשתמש אישר."""
            dialog = SettingsDialog(self)
            if dialog.exec():
                self.load_player_settings()
                self.update_typing_timer_interval()
                self.apply_playback_rate()
                
        def toggle_always_on_top(self):
            """מפעיל/מכבה את מצב 'תמיד למעלה' ומעבר למצב מיני."""
            self.is_always_on_top = not self.is_always_on_top
            self.pin_button.setProperty("active", self.is_always_on_top)
            self.style().polish(self.pin_button)
            
            if self.is_always_on_top:
                self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowStaysOnTopHint)
                self.album_art_label.setVisible(False)
                self.track_info_label.setVisible(False)
                self.artist_album_label.setVisible(False)
                self.setFixedSize(self.mini_size)
            else:
                self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowStaysOnTopHint)
                self.album_art_label.setVisible(True)
                self.track_info_label.setVisible(True)
                self.artist_album_label.setVisible(True)
                self.setMinimumSize(0, 0)
                self.setMaximumSize(QSize(16777215, 16777215))
                self.resize(self.normal_size)
            self.show()
    
        def stop_player(self):
            self.player.stop()
            self.reset_ui_to_default()
            self.current_file_path = None
            
        def reset_ui_to_default(self):
            """מאפס את ממשק המשתמש למצב ההתחלתי."""
            self.track_info_label.setText("לא נטען שיר")
            self.artist_album_label.setText("אמן - אלבום")
            self.album_art_label.setText("Album Art")
            self.album_art_label.setPixmap(QPixmap())
            self.current_time_label.setText("00:00")
            self.total_time_label.setText("00:00")
            self.progress_slider.setValue(0)
        
        def open_file(self):
            file_path, _ = QFileDialog.getOpenFileName(self, "בחר קובץ מוזיקה", "", "קבצי אודיו (*.mp3 *.flac *.wav *.ogg)")
            if file_path:
                self.load_and_play_file(file_path)
    
        def load_and_play_file(self, file_path):
            """טוען קובץ, מציג את המידע שלו ומתחיל לנגן."""
            self.current_file_path = file_path
            metadata = self.get_track_metadata(file_path)
            
            self.track_info_label.setText(metadata['title'])
            self.artist_album_label.setText(f"{metadata['artist']} - {metadata['album']}")
            
            if metadata['pixmap']:
                self.album_art_label.setPixmap(metadata['pixmap'])
            else:
                self.album_art_label.setText("No Art")
                self.album_art_label.setPixmap(QPixmap())
                
            self.player.setSource(QUrl.fromLocalFile(file_path))
            self.apply_playback_rate()
            self.player.play()
    
        def get_track_metadata(self, file_path):
            """קורא מטא-דאטה (כותרת, אמן, תמונת אלבום) מקובץ באמצעות Mutagen."""
            title = os.path.basename(file_path)
            artist = "Unknown Artist"
            album = "Unknown Album"
            pixmap = None
            try:
                if file_path.lower().endswith('.mp3'):
                    audio = MP3(file_path)
                    if 'TIT2' in audio: title = audio['TIT2'].text[0]
                    if 'TPE1' in audio: artist = audio['TPE1'].text[0]
                    if 'TALB' in audio: album = audio['TALB'].text[0]
                    if 'APIC:' in audio:
                        pixmap = QPixmap(); pixmap.loadFromData(audio.tags.getall('APIC:')[0].data)
                elif file_path.lower().endswith('.flac'):
                    audio = FLAC(file_path)
                    if 'title' in audio: title = audio['title'][0]
                    if 'artist' in audio: artist = audio['artist'][0]
                    if 'album' in audio: album = audio['album'][0]
                    if audio.pictures:
                        pixmap = QPixmap(); pixmap.loadFromData(audio.pictures[0].data)
            except Exception as e:
                print(f"Error reading metadata for {file_path}: {e}")
                
            return {'title': title, 'artist': artist, 'album': album, 'pixmap': pixmap}
    
        def play_pause(self):
            if self.player.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
                self.player.pause()
            else:
                if self.current_file_path:
                    self.player.play()
                else:
                    self.open_file()
        
        def seek_by_seconds(self, seconds):
            if not self.current_file_path: return
            current_pos = self.player.position()
            new_pos = current_pos + (seconds * 1000)
            duration = self.player.duration()
            new_pos = max(0, min(new_pos, duration if duration > 0 else new_pos))
            self.player.setPosition(new_pos)
            
        def toggle_mute(self):
            is_muted = not self.audio_output.isMuted()
            self.audio_output.setMuted(is_muted)
            icon = QStyle.StandardPixmap.SP_MediaVolumeMuted if is_muted else QStyle.StandardPixmap.SP_MediaVolume
            self.mute_button.setIcon(self.style().standardIcon(icon))
                
        def set_position(self, position): self.player.setPosition(position)
        def set_volume(self, volume): self.audio_output.setVolume(volume / 100.0)
            
        def update_position(self, position):
            self.progress_slider.setValue(position)
            self.current_time_label.setText(self.format_time(position))
            
        def update_duration(self, duration):
            self.progress_slider.setRange(0, duration)
            self.total_time_label.setText(self.format_time(duration))
            
        def update_play_pause_icon(self, state):
            icon_state = QStyle.StandardPixmap.SP_MediaPause if state == QMediaPlayer.PlaybackState.PlayingState else QStyle.StandardPixmap.SP_MediaPlay
            self.play_pause_button.setIcon(self.style().standardIcon(icon_state))
        
        def handle_media_status(self, status):
            """מטפל בסיום ניגון השיר (למשל, מנגן אותו מחדש)."""
            if status == QMediaPlayer.MediaStatus.EndOfMedia and self.current_file_path:
                self.player.setPosition(0)
                self.player.play()
    
        def format_time(self, ms):
            """ממיר זמן במילישניות לפורמט דקות:שניות (00:00)."""
            s = round(ms / 1000)
            m, s = divmod(s, 60)
            return f"{m:02d}:{s:02d}"
    
        def resume_after_typing(self):
            """מחדש את הניגון לאחר שפרק הזמן ללא הקלדה עבר."""
            if self.was_playing_before_typing:
                if self.player.playbackState() != QMediaPlayer.PlaybackState.PlayingState:
                    self.player.play()
                self.was_playing_before_typing = False
    
    if __name__ == '__main__':
        # נקודת הכניסה הראשית של האפליקציה
        app = QApplication(sys.argv)
        player_window = MusicPlayer()
        player_window.show()
        sys.exit(app.exec())
    

    הפיצ'רים כדלהלן:

    • בעת הקלדה על המקלדת הנגן מפסיק את פעולת הזרמת השמע וממשיך כאשר מפסיקים להקליד

    • ניתן לבטל את הפיצ'ר או להגדיר את זמן ההפסקה בכפתור ההגדרות

    • ניתן להגדיר שבחזרה להזרמת המדיה הנגן יחזור להשמיע מספר שניות אחורה (עד 10 שניות)

    • ניתן להגביר או להנמיך את מהירות הזרמת השמע בחלונית ההגדרות

    • ניתן לנעוץ את הנגן כך שיופיע תמיד מעל כל החלונות וכך תוך כדי הקלדה לדלג קדימה/אחורה בייתר קלות...

    • בעת נעיצת הנגן הוא מצטמצם לגודל מינימאלי שלא יתפוס יותר מידי שטח מסך...

    • ניתן להפעיל ע"י 'פתח באמצעות' (הסבר בהמשך השרשור...)

    • תומך בקבצי אודיו/וידיאו (סיומות נתמכות: MP3, FLAC, WAV, OGG, MP4, MKV, AVI, MOV)

    צילומי מסך:

    7c239351-0684-4c5b-a4d3-fa139ee31b69-image.png
    החלון הראשי

    5b7e1f0e-3ee0-4fa0-8489-0c9ca0d0adeb-image.png
    מצב נעוץ

    44fc312c-1d34-40a6-8567-e234a37f7fe8-image.png
    חלונית ההגדרות

    האדם החושבה תגובה 1 תגובה אחרונה
    11
    • 25802 מנותק
      25802 מנותק
      2580
      מדריכים
      כתב נערך לאחרונה על ידי
      #2

      לכל מי שהוריד היה בעיה בתוכנה שפיצ'ר ההשהיה לא עבד כאשר לא היו בתוך חלון התוכנה הבאג תוקן והקובץ הוחלף בחדש...

      👆👆👆👆👆

      תגובה 1 תגובה אחרונה
      2
      • 25802 מנותק
        25802 מנותק
        2580
        מדריכים
        כתב נערך לאחרונה על ידי 2580
        #3

        הנגן שודרג...

        • לבקשת הציבור התווספה אופציה להגביר או להנמיך את מהירות השמע

        • תוקן באג שבגרסא הקודמת (השניה) כפתורי הדילוג/חזרה לא עבדו

        • חלונית ההגדרות עוצבה מחדש (התמונה למעלה הוחלפה...)

        👆👆👆👆👆

        תגובה 1 תגובה אחרונה
        2
        • 25802 מנותק
          25802 מנותק
          2580
          מדריכים
          כתב נערך לאחרונה על ידי
          #4

          שדרוגים נוספים:

          • ניתן להגדיר שבחזרה להזרמת המדיה הנגן יחזור להשמיע מספר שניות אחורה (עד 10 שניות)

          • התווספה תמיכה בקבצי אודיו/וידיאו (סיומות נתמכות: MP3, FLAC, WAV, OGG, MP4, MKV, AVI, MOV)

          • ניתן להפעיל ע"י 'פתח באמצעות' (סודר באג בסדר ההרצה שגרם לכך שכאשר נפתח בע"י הפתח באמצעות נפתח הנגן אבל לא מזרים את המדיה...)

          הסבר: צריך ללחוץ קליק ימני ואז פתח באמצעות ואז בחר אפליקציה אחרת ואז למטה בחר אפליקציה מהמחשב שלך לדפדף להיכן שנמצא הקובץ EXE ושלהפעיל דרכו...

          לע"ע הוסרה הגירסא שתומכת רק באודיו אם יש דורש תעדכנו ואעלה שוב...

          תגובה 1 תגובה אחרונה
          0
          • 25802 2580

            ייתכן ויש כבר וגם ככה זה לא דבר כזה נצרך אבל לצורך מסויים יצרתי את זה ביחד עם AI לא השקעתי בזה הרבה... בכל אופן זה התוצאה...

            הנגן מיועד לאנשים שמעוניינים להקליד שיעורים וכדו' וקשה להם להמשיך לעקוב אחרי ההמשך תוך כדי הקלדה...

            קישור למג'יקוד (הקובץ חתום) עד שמישהו ( @kasnik אולי?) יעלה את זה לדרייב...

            עריכה: הועלה לדרייב... (קבוע בע"ה) קרדיט - @kasnik

            https://did.li/qlUrl

            קוד המקור... (אל תכעסו אל הארכיטקטורה...)

            התקנות תלויות למי שמשתמש בקוד מקור

            pip install PyQt6 pynput mutagen
            

            נגן_הקלדה.py

            או הקוד עצמו בספויילר

            import sys
            import os
            from functools import partial
            from pynput import keyboard
            from PyQt6.QtCore import (
                QThread, pyqtSignal, QObject, QUrl, Qt, QTimer, QSize, QSettings,
                QPropertyAnimation, pyqtProperty
            )
            from PyQt6.QtWidgets import (
                QApplication, QMainWindow, QWidget, QPushButton, QSlider, QLabel,
                QVBoxLayout, QHBoxLayout, QFileDialog, QStyle, QDialog,
                QDoubleSpinBox, QDialogButtonBox, QGroupBox, QAbstractSpinBox
            )
            from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput
            from PyQt6.QtGui import QPixmap, QFont, QPainter, QBrush, QColor
            from mutagen.mp3 import MP3
            from mutagen.flac import FLAC
            
            
            # --- ווידג'ט מתג ויזואלי ---
            class SlidingToggleSwitch(QWidget):
                """מחלקה המייצרת מתג החלקה ויזואלי (Toggle Switch)."""
                toggled = pyqtSignal(bool)
            
                def __init__(self, parent=None):
                    super().__init__(parent)
                    self._width = 40
                    self._height = 20
                    self.setFixedSize(self._width, self._height)
                    self.setCursor(Qt.CursorShape.PointingHandCursor)
            
                    self._padding = 2
                    self._circle_diameter = self._height - self._padding * 2
                    self._x_off = self._padding
                    self._x_on = self._width - self._circle_diameter - self._padding
            
                    self._checked = False
                    self._circle_pos = self._x_off
            
                    # הגדרת אנימציה להזזת העיגול
                    self.animation = QPropertyAnimation(self, b"circle_pos")
                    self.animation.setDuration(150)
            
                def mousePressEvent(self, event):
                    self._checked = not self._checked
                    self.toggled.emit(self._checked)
                    self.animate()
                    super().mousePressEvent(event)
            
                def paintEvent(self, event):
                    """מצייר את המתג בהתאם למצבו (דולק/כבוי)."""
                    painter = QPainter(self)
                    painter.setRenderHint(QPainter.RenderHint.Antialiasing)
                    bg_color = QColor("#3498DB" if self._checked else "#BDC3C7")
                    painter.setBrush(QBrush(bg_color))
                    painter.setPen(Qt.PenStyle.NoPen)
                    painter.drawRoundedRect(0, 0, self._width, self._height, self._height / 2, self._height / 2)
                    painter.setBrush(QBrush(QColor("#FFFFFF")))
                    painter.drawEllipse(int(self._circle_pos), self._padding, self._circle_diameter, self._circle_diameter)
            
                def animate(self):
                    end_pos = self._x_on if self._checked else self._x_off
                    self.animation.setStartValue(self._circle_pos)
                    self.animation.setEndValue(end_pos)
                    self.animation.start()
            
                def get_circle_pos(self):
                    return self._circle_pos
            
                def set_circle_pos(self, pos):
                    self._circle_pos = pos
                    self.update()
            
                # מאפיין המאפשר אנימציה חלקה של מיקום העיגול
                circle_pos = pyqtProperty(float, get_circle_pos, set_circle_pos)
            
                def isChecked(self):
                    return self._checked
            
                def setChecked(self, checked):
                    if self._checked == checked:
                        return
                    self._checked = checked
                    self._circle_pos = self._x_on if checked else self._x_off
                    self.toggled.emit(checked)
                    self.update()
            
            # --- עיצוב כהה (Dark Mode) עבור כלל האפליקציה ---
            DARK_STYLESHEET = """
            QWidget {
                background-color: #2b2b2b;
                color: #f0f0f0;
                font-family: Arial, sans-serif;
            }
            QMainWindow {
                background-color: #2b2b2b;
            }
            QPushButton {
                background-color: #555;
                border: 1px solid #666;
                padding: 8px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #666;
            }
            QPushButton:pressed {
                background-color: #444;
            }
            QPushButton#ControlButton {
                border: none;
                font-size: 18px;
                font-weight: bold;
                background-color: #3c3c3c;
                border-radius: 22px;
            }
            QPushButton#ControlButton:hover {
                background-color: #505050;
            }
            QPushButton#ControlButton:pressed {
                background-color: #2a2a2a;
            }
            QPushButton#ControlButton[active="true"] {
                background-color: #5a9bcf;
                color: white;
            }
            QLabel#AlbumArtLabel {
                border: 2px solid #444;
                border-radius: 5px;
                background-color: #3c3c3c;
            }
            QLabel#TrackInfoLabel {
                font-size: 16px;
                font-weight: bold;
                padding-bottom: 5px;
            }
            QLabel#ArtistAlbumLabel {
                font-size: 12px;
                color: #ccc;
                padding-bottom: 10px;
            }
            QSlider::groove:horizontal {
                border: 1px solid #444;
                height: 8px;
                background: #3c3c3c;
                margin: 2px 0;
                border-radius: 4px;
            }
            QSlider::sub-page:horizontal {
                background: #5a9bcf;
                border: 1px solid #444;
                height: 8px;
                border-radius: 4px;
            }
            QSlider::handle:horizontal {
                background: white;
                border: 1px solid #aaa;
                width: 16px;
                margin: -5px 0;
                border-radius: 8px;
            }
            QDialog {
                background-color: #3c3c3c;
            }
            QGroupBox {
                font-size: 14px;
                font-weight: bold;
                border: 1px solid #444;
                border-radius: 5px;
                margin-top: 10px;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                subcontrol-position: top center;
                padding: 0 5px;
                background-color: #3c3c3c;
            }
            QDoubleSpinBox {
                background-color: #2b2b2b;
                border: 1px solid #666;
                padding: 5px;
                border-radius: 4px;
                font-size: 14px;
            }
            QDialogButtonBox QPushButton {
                padding: 5px 15px;
            }
            """
            
            # --- חלון דיאלוג להגדרות האפליקציה ---
            class SettingsDialog(QDialog):
                """מנהל את הגדרות המשתמש כמו השהייה בזמן הקלדה ומהירות ניגון."""
                def __init__(self, parent=None):
                    super().__init__(parent)
                    self.setWindowTitle("הגדרות")
                    self.setModal(True)
                    self.setMinimumWidth(420)
                    self.setLayoutDirection(Qt.LayoutDirection.RightToLeft)
                    
                    # שימוש ב-QSettings לשמירת הגדרות בין הפעלות
                    self.settings = QSettings("MyMusicPlayer", "Settings")
            
                    main_layout = QVBoxLayout(self)
                    main_layout.setSpacing(15)
                    main_layout.setContentsMargins(15, 15, 15, 15)
            
                    typing_group = QGroupBox("השהייה בזמן הקלדה")
                    typing_layout = QHBoxLayout()
                    typing_layout.setContentsMargins(10, 15, 10, 10)
                    typing_layout.setSpacing(10)
                    
                    self.typing_pause_switch = SlidingToggleSwitch()
                    self.delay_spinbox = QDoubleSpinBox()
                    self.delay_spinbox.setRange(0.5, 10.0)
                    self.delay_spinbox.setSingleStep(0.1)
                    self.delay_spinbox.setSuffix(" שניות")
                    self.delay_spinbox.setAlignment(Qt.AlignmentFlag.AlignCenter)
                    self.delay_spinbox.setButtonSymbols(QAbstractSpinBox.ButtonSymbols.NoButtons)
                    self.delay_spinbox.setMinimumWidth(80)
            
                    self.delay_down_button = QPushButton("-")
                    self.delay_down_button.setFixedSize(30, 30)
                    self.delay_up_button = QPushButton("+")
                    self.delay_up_button.setFixedSize(30, 30)
            
                    typing_layout.addWidget(QLabel("הפעלה:"))
                    typing_layout.addWidget(self.typing_pause_switch)
                    typing_layout.addStretch()
                    typing_layout.addWidget(QLabel("זמן:"))
                    typing_layout.addWidget(self.delay_spinbox)
                    typing_layout.addWidget(self.delay_down_button)
                    typing_layout.addWidget(self.delay_up_button)
                    typing_group.setLayout(typing_layout)
                    main_layout.addWidget(typing_group)
                    
                    rate_group = QGroupBox("מהירות ניגון")
                    rate_container = QWidget()
                    rate_layout = QHBoxLayout(rate_container)
                    rate_layout.setContentsMargins(10, 15, 10, 10)
                    rate_layout.setSpacing(10)
                    rate_container.setLayoutDirection(Qt.LayoutDirection.LeftToRight)
            
                    self.decrease_rate_button = QPushButton("◀")
                    self.decrease_rate_button.setFixedSize(30, 30)
                    self.playback_rate_slider = QSlider(Qt.Orientation.Horizontal)
                    self.playback_rate_slider.setRange(25, 400) # מייצג 0.25x עד 4.0x
                    self.playback_rate_slider.setSingleStep(5)
                    self.increase_rate_button = QPushButton("▶")
                    self.increase_rate_button.setFixedSize(30, 30)
                    self.playback_rate_label = QLabel("1.00 x")
                    self.playback_rate_label.setFixedWidth(55)
                    self.playback_rate_label.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
            
                    rate_layout.addWidget(self.decrease_rate_button)
                    rate_layout.addWidget(self.playback_rate_slider)
                    rate_layout.addWidget(self.increase_rate_button)
                    rate_layout.addWidget(self.playback_rate_label)
                    group_main_layout = QVBoxLayout(rate_group)
                    group_main_layout.addWidget(rate_container)
                    main_layout.addWidget(rate_group)
                    
                    self.connect_settings_signals()
                    
                    button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
                    button_box.setLayoutDirection(Qt.LayoutDirection.LeftToRight)
                    button_box.accepted.connect(self.accept)
                    button_box.rejected.connect(self.reject)
                    
                    main_layout.addStretch(1)
                    main_layout.addWidget(button_box)
                    
                    self.load_settings()
                    
                def connect_settings_signals(self):
                    self.playback_rate_slider.valueChanged.connect(self.update_rate_label)
                    self.decrease_rate_button.clicked.connect(self.decrease_rate)
                    self.increase_rate_button.clicked.connect(self.increase_rate)
                    self.delay_down_button.clicked.connect(self.decrease_delay)
                    self.delay_up_button.clicked.connect(self.increase_delay)
                    
                def decrease_delay(self): self.delay_spinbox.stepDown()
                def increase_delay(self): self.delay_spinbox.stepUp()
                def decrease_rate(self): self.playback_rate_slider.setValue(self.playback_rate_slider.value() - 25)
                def increase_rate(self): self.playback_rate_slider.setValue(self.playback_rate_slider.value() + 25)
                def update_rate_label(self, value): self.playback_rate_label.setText(f"{value / 100.0:.2f} x")
                    
                def load_settings(self):
                    """טוען הגדרות שמורות מהפעלה קודמת."""
                    typing_pause_enabled = self.settings.value("typingPauseEnabled", True, type=bool)
                    delay_time = self.settings.value("delayTime", 1.5, type=float)
                    playback_rate = self.settings.value("playbackRate", 1.0, type=float)
                    
                    self.typing_pause_switch.setChecked(typing_pause_enabled)
                    self.delay_spinbox.setValue(delay_time)
                    slider_value = int(playback_rate * 100)
                    self.playback_rate_slider.setValue(slider_value)
                    self.update_rate_label(slider_value)
            
                def save_settings(self):
                    """שומר את ההגדרות הנוכחיות."""
                    self.settings.setValue("typingPauseEnabled", self.typing_pause_switch.isChecked())
                    self.settings.setValue("delayTime", self.delay_spinbox.value())
                    self.settings.setValue("playbackRate", self.playback_rate_slider.value() / 100.0)
            
                def accept(self):
                    self.save_settings()
                    super().accept()
            
            # --- מאזין גלובלי למקלדת ---
            class GlobalKeyListener(QObject):
                """
                אובייקט המאזין להקשות מקלדת ברמה הגלובלית.
                הוא יועבר ל-Thread נפרד כדי לא להקפיא את ממשק המשתמש.
                """
                keyPressed = pyqtSignal()
                
                def __init__(self):
                    super().__init__()
                    self.listener = keyboard.Listener(on_press=self.on_press)
            
                def on_press(self, key):
                    self.keyPressed.emit()
                    
                def start_listening(self):
                    self.listener.start()
                    self.listener.join()
                    
                def stop_listening(self):
                    self.listener.stop()
            
            # --- חלון הנגן הראשי ---
            class MusicPlayer(QMainWindow):
                def __init__(self):
                    super().__init__()
            
                    self.normal_size = QSize(520, 600)
                    self.mini_size = QSize(460, 160)
                    
                    self.setWindowTitle("נגן הקלדה")
                    self.setGeometry(100, 100, self.normal_size.width(), self.normal_size.height())
                    self.setWindowIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaPlay))
            
                    self.settings = QSettings("MyMusicPlayer", "Settings")
                    self.player = QMediaPlayer()
                    self.audio_output = QAudioOutput()
                    self.player.setAudioOutput(self.audio_output)
                    
                    self.current_file_path = None
                    self.is_always_on_top = False
                    self.was_playing_before_typing = False
                    
                    self.typing_pause_enabled = True
                    self.delay_time = 1.5
                    self.playback_rate = 1.0
                    
                    self.typing_timer = QTimer(self)
                    self.typing_timer.setSingleShot(True)
            
                    self.load_player_settings()
                    self.update_typing_timer_interval()
                    self.apply_playback_rate()
            
                    self.init_ui()
                    self.connect_signals()
                    
                    self.setup_global_key_listener()
                    
                    self.setStyleSheet(DARK_STYLESHEET)
                
                def setup_global_key_listener(self):
                    """מגדיר ומפעיל את המאזין הגלובלי למקלדת ב-Thread נפרד."""
                    self.key_listener_thread = QThread()
                    self.key_listener = GlobalKeyListener()
                    
                    self.key_listener.moveToThread(self.key_listener_thread)
                    self.key_listener.keyPressed.connect(self.handle_global_key_press)
                    self.key_listener_thread.started.connect(self.key_listener.start_listening)
                    self.key_listener_thread.start()
            
                def handle_global_key_press(self):
                    """מטפל בלחיצת מקש גלובלית: משהה את הנגן ומפעיל טיימר לחידוש."""
                    if self.typing_pause_enabled:
                        if self.player.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
                            self.was_playing_before_typing = True
                            self.player.pause()
                        self.typing_timer.start()
            
                def closeEvent(self, event):
                    """מבטיח סגירה נקייה של ה-Thread של המאזין בעת יציאה מהאפליקציה."""
                    self.key_listener.stop_listening() 
                    self.key_listener_thread.quit()    
                    self.key_listener_thread.wait()    
                    event.accept() 
                    
                def load_player_settings(self):
                    """טוען הגדרות מה-QSettings ומחיל אותן על הנגן."""
                    self.typing_pause_enabled = self.settings.value("typingPauseEnabled", True, type=bool)
                    self.delay_time = self.settings.value("delayTime", 1.5, type=float)
                    self.playback_rate = self.settings.value("playbackRate", 1.0, type=float)
            
                def update_typing_timer_interval(self):
                    self.typing_timer.setInterval(int(self.delay_time * 1000)) # המרה לשניות
            
                def apply_playback_rate(self):
                    self.player.setPlaybackRate(self.playback_rate)
                    
                def init_ui(self):
                    """בונה את כל רכיבי ממשק המשתמש."""
                    central_widget = QWidget()
                    self.setCentralWidget(central_widget)
                    main_layout = QVBoxLayout(central_widget)
            
                    # --- שורת כלים עליונה ---
                    top_bar_layout = QHBoxLayout()
                    self.open_file_button = QPushButton("פתח קובץ")
                    self.pin_button = QPushButton("📌")
                    self.pin_button.setToolTip("הצמד למעלה (מצב מיני)")
                    self.pin_button.setFixedSize(45, 45)
                    self.pin_button.setObjectName("ControlButton")
                    self.pin_button.setProperty("active", self.is_always_on_top)
                    font_pin = self.pin_button.font(); font_pin.setPointSize(20); self.pin_button.setFont(font_pin)
                    self.settings_button = QPushButton("⚙️")
                    self.settings_button.setToolTip("הגדרות")
                    self.settings_button.setFixedSize(45, 45)
                    self.settings_button.setObjectName("ControlButton")
                    font_settings = self.settings_button.font(); font_settings.setPointSize(22); self.settings_button.setFont(font_settings)
                    self.mute_button = QPushButton()
                    self.mute_button.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaVolume))
                    self.mute_button.setFixedSize(45, 45)
                    self.mute_button.setObjectName("ControlButton")
                    self.mute_button.setToolTip("השתק / בטל השתקה")
                    self.volume_slider = QSlider(Qt.Orientation.Horizontal)
                    self.volume_slider.setRange(0, 100)
                    self.volume_slider.setValue(70)
                    self.audio_output.setVolume(0.7)
                    self.volume_slider.setToolTip("עוצמת שמע")
            
                    top_bar_layout.addWidget(self.open_file_button)
                    top_bar_layout.addStretch()
                    top_bar_layout.addWidget(self.mute_button)
                    top_bar_layout.addWidget(self.volume_slider)
                    top_bar_layout.addStretch()
                    top_bar_layout.addWidget(self.pin_button)
                    top_bar_layout.addWidget(self.settings_button)
                    
                    # --- תצוגת מידע ואלבום ---
                    self.album_art_label = QLabel("Album Art")
                    self.album_art_label.setObjectName("AlbumArtLabel")
                    self.album_art_label.setScaledContents(True)
                    self.album_art_label.setMinimumSize(300, 300)
                    self.album_art_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
                    self.track_info_label = QLabel("לא נטען שיר")
                    self.track_info_label.setObjectName("TrackInfoLabel")
                    self.track_info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
                    self.artist_album_label = QLabel("אמן - אלבום")
                    self.artist_album_label.setObjectName("ArtistAlbumLabel")
                    self.artist_album_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
            
                    # --- סליידר התקדמות ---
                    progress_layout = QHBoxLayout()
                    self.current_time_label = QLabel("00:00")
                    self.progress_slider = QSlider(Qt.Orientation.Horizontal)
                    self.total_time_label = QLabel("00:00")
                    self.progress_slider.setToolTip("התקדמות השיר")
                    progress_layout.addWidget(self.current_time_label)
                    progress_layout.addWidget(self.progress_slider)
                    progress_layout.addWidget(self.total_time_label)
            
                    # --- כפתורי שליטה ---
                    controls_layout = QHBoxLayout()
                    button_size = QSize(45, 45)
                    
                    self.seek_back_20_button = QPushButton("⏮️")
                    self.seek_back_10_button = QPushButton("⏪")
                    self.seek_back_5_button = QPushButton("◀️")
                    self.stop_button = QPushButton()
                    self.play_pause_button = QPushButton()
                    self.seek_fwd_5_button = QPushButton("▶️")
                    self.seek_fwd_10_button = QPushButton("⏩")
                    self.seek_fwd_20_button = QPushButton("⏭️")
                    
                    self.stop_button.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaStop))
                    self.play_pause_button.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaPlay))
                    self.play_pause_button.setIconSize(QSize(24, 24))
                    self.stop_button.setIconSize(QSize(20, 20))
                    
                    control_buttons = [
                        (self.seek_back_20_button, "הרץ 20 שניות אחורה"), (self.seek_back_10_button, "הרץ 10 שניות אחורה"),
                        (self.seek_back_5_button, "הרץ 5 שניות אחורה"), (self.stop_button, "עצור נגינה"),
                        (self.play_pause_button, "נגן / השהה"), (self.seek_fwd_5_button, "הרץ 5 שניות קדימה"),
                        (self.seek_fwd_10_button, "הרץ 10 שניות קדימה"), (self.seek_fwd_20_button, "הרץ 20 שניות קדימה")
                    ]
                    
                    controls_layout.addStretch()
                    for button, tooltip in control_buttons:
                        button.setFixedSize(button_size)
                        button.setObjectName("ControlButton")
                        button.setToolTip(tooltip)
                        controls_layout.addWidget(button)
                    controls_layout.addStretch()
                    
                    # --- הרכבת הממשק הראשי ---
                    main_layout.addLayout(top_bar_layout)
                    main_layout.addStretch(1)
                    main_layout.addWidget(self.album_art_label)
                    main_layout.addWidget(self.track_info_label)
                    main_layout.addWidget(self.artist_album_label)
                    main_layout.addLayout(progress_layout)
                    main_layout.addLayout(controls_layout)
                    main_layout.addStretch(1)
            
                def connect_signals(self):
                    """מחבר את כל הסיגנלים של הווידג'טים לפונקציות (סלוטים) המתאימות."""
                    self.open_file_button.clicked.connect(self.open_file)
                    self.play_pause_button.clicked.connect(self.play_pause)
                    self.stop_button.clicked.connect(self.stop_player)
                    self.mute_button.clicked.connect(self.toggle_mute)
                    self.settings_button.clicked.connect(self.open_settings_dialog)
                    self.pin_button.clicked.connect(self.toggle_always_on_top)
            
                    seek_map = {
                        self.seek_back_20_button: -20, self.seek_back_10_button: -10, self.seek_back_5_button: -5,
                        self.seek_fwd_5_button: 5, self.seek_fwd_10_button: 10, self.seek_fwd_20_button: 20
                    }
                    
                    # שימוש ב-partial כדי להעביר ארגומנט (מספר שניות) לפונקציית ה-seek
                    for button, seconds in seek_map.items():
                        button.clicked.connect(partial(self.seek_by_seconds, seconds))
            
                    # חיבור סיגנלים מהנגן עצמו לעדכון הממשק
                    self.player.positionChanged.connect(self.update_position)
                    self.player.durationChanged.connect(self.update_duration)
                    self.player.playbackStateChanged.connect(self.update_play_pause_icon)
                    self.player.mediaStatusChanged.connect(self.handle_media_status)
                    
                    self.progress_slider.sliderMoved.connect(self.set_position)
                    self.volume_slider.valueChanged.connect(self.set_volume)
                    self.typing_timer.timeout.connect(self.resume_after_typing)
            
                def open_settings_dialog(self):
                    """פותח את חלון ההגדרות ומחיל שינויים אם המשתמש אישר."""
                    dialog = SettingsDialog(self)
                    if dialog.exec():
                        self.load_player_settings()
                        self.update_typing_timer_interval()
                        self.apply_playback_rate()
                        
                def toggle_always_on_top(self):
                    """מפעיל/מכבה את מצב 'תמיד למעלה' ומעבר למצב מיני."""
                    self.is_always_on_top = not self.is_always_on_top
                    self.pin_button.setProperty("active", self.is_always_on_top)
                    self.style().polish(self.pin_button)
                    
                    if self.is_always_on_top:
                        self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowStaysOnTopHint)
                        self.album_art_label.setVisible(False)
                        self.track_info_label.setVisible(False)
                        self.artist_album_label.setVisible(False)
                        self.setFixedSize(self.mini_size)
                    else:
                        self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowStaysOnTopHint)
                        self.album_art_label.setVisible(True)
                        self.track_info_label.setVisible(True)
                        self.artist_album_label.setVisible(True)
                        self.setMinimumSize(0, 0)
                        self.setMaximumSize(QSize(16777215, 16777215))
                        self.resize(self.normal_size)
                    self.show()
            
                def stop_player(self):
                    self.player.stop()
                    self.reset_ui_to_default()
                    self.current_file_path = None
                    
                def reset_ui_to_default(self):
                    """מאפס את ממשק המשתמש למצב ההתחלתי."""
                    self.track_info_label.setText("לא נטען שיר")
                    self.artist_album_label.setText("אמן - אלבום")
                    self.album_art_label.setText("Album Art")
                    self.album_art_label.setPixmap(QPixmap())
                    self.current_time_label.setText("00:00")
                    self.total_time_label.setText("00:00")
                    self.progress_slider.setValue(0)
                
                def open_file(self):
                    file_path, _ = QFileDialog.getOpenFileName(self, "בחר קובץ מוזיקה", "", "קבצי אודיו (*.mp3 *.flac *.wav *.ogg)")
                    if file_path:
                        self.load_and_play_file(file_path)
            
                def load_and_play_file(self, file_path):
                    """טוען קובץ, מציג את המידע שלו ומתחיל לנגן."""
                    self.current_file_path = file_path
                    metadata = self.get_track_metadata(file_path)
                    
                    self.track_info_label.setText(metadata['title'])
                    self.artist_album_label.setText(f"{metadata['artist']} - {metadata['album']}")
                    
                    if metadata['pixmap']:
                        self.album_art_label.setPixmap(metadata['pixmap'])
                    else:
                        self.album_art_label.setText("No Art")
                        self.album_art_label.setPixmap(QPixmap())
                        
                    self.player.setSource(QUrl.fromLocalFile(file_path))
                    self.apply_playback_rate()
                    self.player.play()
            
                def get_track_metadata(self, file_path):
                    """קורא מטא-דאטה (כותרת, אמן, תמונת אלבום) מקובץ באמצעות Mutagen."""
                    title = os.path.basename(file_path)
                    artist = "Unknown Artist"
                    album = "Unknown Album"
                    pixmap = None
                    try:
                        if file_path.lower().endswith('.mp3'):
                            audio = MP3(file_path)
                            if 'TIT2' in audio: title = audio['TIT2'].text[0]
                            if 'TPE1' in audio: artist = audio['TPE1'].text[0]
                            if 'TALB' in audio: album = audio['TALB'].text[0]
                            if 'APIC:' in audio:
                                pixmap = QPixmap(); pixmap.loadFromData(audio.tags.getall('APIC:')[0].data)
                        elif file_path.lower().endswith('.flac'):
                            audio = FLAC(file_path)
                            if 'title' in audio: title = audio['title'][0]
                            if 'artist' in audio: artist = audio['artist'][0]
                            if 'album' in audio: album = audio['album'][0]
                            if audio.pictures:
                                pixmap = QPixmap(); pixmap.loadFromData(audio.pictures[0].data)
                    except Exception as e:
                        print(f"Error reading metadata for {file_path}: {e}")
                        
                    return {'title': title, 'artist': artist, 'album': album, 'pixmap': pixmap}
            
                def play_pause(self):
                    if self.player.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
                        self.player.pause()
                    else:
                        if self.current_file_path:
                            self.player.play()
                        else:
                            self.open_file()
                
                def seek_by_seconds(self, seconds):
                    if not self.current_file_path: return
                    current_pos = self.player.position()
                    new_pos = current_pos + (seconds * 1000)
                    duration = self.player.duration()
                    new_pos = max(0, min(new_pos, duration if duration > 0 else new_pos))
                    self.player.setPosition(new_pos)
                    
                def toggle_mute(self):
                    is_muted = not self.audio_output.isMuted()
                    self.audio_output.setMuted(is_muted)
                    icon = QStyle.StandardPixmap.SP_MediaVolumeMuted if is_muted else QStyle.StandardPixmap.SP_MediaVolume
                    self.mute_button.setIcon(self.style().standardIcon(icon))
                        
                def set_position(self, position): self.player.setPosition(position)
                def set_volume(self, volume): self.audio_output.setVolume(volume / 100.0)
                    
                def update_position(self, position):
                    self.progress_slider.setValue(position)
                    self.current_time_label.setText(self.format_time(position))
                    
                def update_duration(self, duration):
                    self.progress_slider.setRange(0, duration)
                    self.total_time_label.setText(self.format_time(duration))
                    
                def update_play_pause_icon(self, state):
                    icon_state = QStyle.StandardPixmap.SP_MediaPause if state == QMediaPlayer.PlaybackState.PlayingState else QStyle.StandardPixmap.SP_MediaPlay
                    self.play_pause_button.setIcon(self.style().standardIcon(icon_state))
                
                def handle_media_status(self, status):
                    """מטפל בסיום ניגון השיר (למשל, מנגן אותו מחדש)."""
                    if status == QMediaPlayer.MediaStatus.EndOfMedia and self.current_file_path:
                        self.player.setPosition(0)
                        self.player.play()
            
                def format_time(self, ms):
                    """ממיר זמן במילישניות לפורמט דקות:שניות (00:00)."""
                    s = round(ms / 1000)
                    m, s = divmod(s, 60)
                    return f"{m:02d}:{s:02d}"
            
                def resume_after_typing(self):
                    """מחדש את הניגון לאחר שפרק הזמן ללא הקלדה עבר."""
                    if self.was_playing_before_typing:
                        if self.player.playbackState() != QMediaPlayer.PlaybackState.PlayingState:
                            self.player.play()
                        self.was_playing_before_typing = False
            
            if __name__ == '__main__':
                # נקודת הכניסה הראשית של האפליקציה
                app = QApplication(sys.argv)
                player_window = MusicPlayer()
                player_window.show()
                sys.exit(app.exec())
            

            הפיצ'רים כדלהלן:

            • בעת הקלדה על המקלדת הנגן מפסיק את פעולת הזרמת השמע וממשיך כאשר מפסיקים להקליד

            • ניתן לבטל את הפיצ'ר או להגדיר את זמן ההפסקה בכפתור ההגדרות

            • ניתן להגדיר שבחזרה להזרמת המדיה הנגן יחזור להשמיע מספר שניות אחורה (עד 10 שניות)

            • ניתן להגביר או להנמיך את מהירות הזרמת השמע בחלונית ההגדרות

            • ניתן לנעוץ את הנגן כך שיופיע תמיד מעל כל החלונות וכך תוך כדי הקלדה לדלג קדימה/אחורה בייתר קלות...

            • בעת נעיצת הנגן הוא מצטמצם לגודל מינימאלי שלא יתפוס יותר מידי שטח מסך...

            • ניתן להפעיל ע"י 'פתח באמצעות' (הסבר בהמשך השרשור...)

            • תומך בקבצי אודיו/וידיאו (סיומות נתמכות: MP3, FLAC, WAV, OGG, MP4, MKV, AVI, MOV)

            צילומי מסך:

            7c239351-0684-4c5b-a4d3-fa139ee31b69-image.png
            החלון הראשי

            5b7e1f0e-3ee0-4fa0-8489-0c9ca0d0adeb-image.png
            מצב נעוץ

            44fc312c-1d34-40a6-8567-e234a37f7fe8-image.png
            חלונית ההגדרות

            האדם החושבה מנותק
            האדם החושבה מנותק
            האדם החושב
            מדריכים
            כתב נערך לאחרונה על ידי
            #5

            @2580 https://mitmachim.top/topic/57015/להורדה-נגן-שמיוחד-לתמלול-עם-קישור?_=1752054973896

            תגובה 1 תגובה אחרונה
            1

            • התחברות

            • אין לך חשבון עדיין? הרשמה

            • התחברו או הירשמו כדי לחפש.
            • פוסט ראשון
              פוסט אחרון
            0
            • חוקי הפורום
            • פופולרי
            • לא נפתר
            • משתמשים
            • חיפוש גוגל בפורום
            • צור קשר