Petrichor

kushalpandyaMIT活跃维护

一款完全离线的 macOS 音乐播放器,用 Swift 编写,支持 MP3、OGG Vorbis 等格式,适合不想依赖流媒体、只想播放本地音乐库的用户。

An offline macOS music player

v1.7.3发布于 2026-11-01 18:17 UTC1,766star +6 / 7 天77fork同步于 2026-11-11 05:44 UTC

怎么上手

由 README 提炼,执行前请确认
从 Releases 页面下载最新 .dmg 安装包并拖入 Applications 即可运行
硬件要求按 README 说明

README 未说明具体硬件要求;仅支持 macOS,未提及 CPU 或 GPU 型号限制。

支持 Apple Silicon
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

macOS 上的离线音乐播放器,本地曲库管理与高保真播放一站搞定

一款完全离线的 macOS 音乐播放器,用 Swift 编写,支持 MP3、OGG Vorbis 等格式,适合不想依赖流媒体、只想播放本地音乐库的用户。

核心能力

  • 支持 46 种音频扩展名,含 FLAC、DSD、APE 等无损格式
  • 映射本地音乐文件夹并按专辑、艺术家、年份组织浏览
  • 支持条件、否定与正则规则构建智能播放列表
  • 支持无缝播放、交叉淡入、ReplayGain、均衡器与空间音频
  • 集成 Last.fm 记录、Siri 与自动化 API、菜单栏与 Dock 控制

容易踩的坑

不支持 DRM 保护文件、有声书容器 .m4b、RealAudio 与 MIDI;1.7 起不再支持 MOD/IT/S3M/XM 模块格式;多数功能依赖曲目元数据质量,元数据差会导致整理与智能列表失效;自行构建 .dmg 需 Xcode 及 xcpretty、create-dmg,公证还需付费 Apple 开发者账号

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v1.7.3
  1. v1.7.3最新正式版2026-11-01 · 2026-11-01 18:17 UTC

    Fixes

    • Fix library scans never completing when some subfolders can't be read, such as folders on a NAS that the SMB client fails to open (#414)
    • Fix mini player occasionally becoming unresponsive, mostly when opened from the menu bar (#415)
    • Fix volume slider animating in from the top left on views with tracks in macOS 27 (#400) (Thanks @mdas39!)

    Others

    • Update menu icons to follow macOS 27 guidelines, which also fixes menu icons not showing at all on macOS 27 (#409)

    Full Changelog: https://github.com/kushalpandya/Petrichor/compare/v1.7.2...v1.7.3

  2. v1.7.22026-10-02 · 2026-10-02 01:37 UTC
    查看更新说明

    Fixes

    • Prevent library data loss when refreshing folders while some are unavailable, such as disconnected external drives (#395)
    • Prevent an occasional app crash when opening Immersive mode (#396)
    • Fetch synced lyrics from LRCLIB for tracks that only have untimed embedded lyrics (#377) (Thanks @AmanuelCh!)

    Full Changelog: https://github.com/kushalpandya/Petrichor/compare/v1.7.1...v1.7.2

  3. v1.7.12026-09-21 · 2026-09-21 23:14 UTC
    查看更新说明

    Fixes

    • Automatically pick up folder renames instead of leaving track references unusable (#390)
    • Fix tracks list navigation not working via Up/Down arrow keys and highlight not following system accent color on macOS 27 (#392)
    • Prevent crashes when the same folder is present in the library via multiple routes (#386)
    • Fix albums with the same name but different artists being conflated (#380)
    • Preserve duplicate tracks explicitly added to playlists (#381)

    Others

    • Animate lyrics view scrolling during playback (#384) (Thanks @hemingtsai)
    • Update Pin/Unpin button icons for better usability (#387)

    Full Changelog: https://github.com/kushalpandya/Petrichor/compare/v1.7.0...v1.7.1

  4. v1.7.02026-09-03 · 2026-09-03 22:33 UTC
    查看更新说明

    Internet Radio, Crossfade & ReplayGain, AirPlay 2, redesigned Library and Discover, custom playlist artworks, and up to 4x lower CPU usage

    [!IMPORTANT] This is the last release to support macOS 14 (Sonoma) and Intel Macs. v1.7.x will continue to receive point fixes on macOS 14 and Intel when necessary, but the next major release will require macOS 15 or later and Apple Silicon.

    Features

    • Add support for Internet Radio with manual HLS stations, popular stations from radio-browser.info, and station collections (#352, fixes #100)
    • Add AirPlay 2 support to play music on compatible devices (#363, fixes #112)
    • Add Crossfade between tracks, configurable in Settings > General > Playback (#347, fixes #266)
    • Add ReplayGain volume normalization with gain source and preamp selection (#348, fixes #102)
    • Redesign Discover with Featured, Recently Played, and Most Loved & Played sections (#350)
    • Redesign Library tab into a 3-column layout, and allow pinning any entity type to the Home sidebar (#356)
    • Add support for using a custom local or online image as artwork for artists and playlists (#359)
    • Add support for negative operators (is not, does not contain, does not start with, does not end with) and regular expressions in Smart Playlists (#358)
    • Group tracks by album when viewed under Artist, Album Artist, and Composer, with per-group sorting and play controls (#371, fixes #90, #95, #99)
    • Add disc number sections for tracks within multi-disc albums (#369, fixes #120)
    • Show the full list of supported audio formats in a grouped popover, reachable from Settings > Library (#346, fixes #211)
    • Add Mini Player action to the menubar dropdown (#365)
    • Revamp the onboarding experience for new users (#353)

    Enhancements

    • Remove SFBAudioEngine and make CrescendoKit the default (and only) media engine for playback and metadata extraction (#343)
      • Note: this drops playback support for the MOD, IT, S3M, and XM tracker formats, and adds a large number of new formats in return. See the updated list in the README or the in-app formats popover.
      • Thanks to Stephen Booth for building and maintaining SFBAudioEngine all these years.
    • Reduce CPU usage during playback by up to 4 times, idle to ~0% CPU when nothing is playing, and automatically engage macOS App Nap when eligible (#364, fixes #274)
    • Speed up app launch by rewriting state management and restoration (#353)
    • Adapt playback queue management and Now Playing publishing to Crescendo APIs (#344)
    • Add Years to the Discover carousels alongside albums, artists, genres, and playlists (#373)
    • Allow toggling off Internet Radio and artist info refreshing, and make artist info fetching fail-safe (#366)
    • Give Genre, Year, Decade, and Folder entities their own distinct procedural artwork, and animate dominant artwork colours on first load (#356)
    • Harden database integrity against invalid album years, make track and orphan cleanup atomic and scalable, and clear dangling pins and entities (#372)
    • Hide scrollbars in the lyrics view for a cleaner, more immersive look (#340) (Thanks @akkva!)

    Fixes

    • Fix the track Go-to menu incorrectly splitting artist names containing separators like & or / (#338, fixes #337)
    • Fix tracks occasionally showing incorrect artwork (#368, fixes #357)
    • Fix bilingual lyrics highlighting for lines sharing the same timestamp (#342) (Thanks @akkva!)
    • Prevent the track details sidebar from shrinking past a minimum width while resizing (#367, fixes #355)
    • Fix a regression where advancing tracks could behave incorrectly, plus assorted UI polish across the app (#356, #374)

    Others

    • Consolidate all post-v1.6.1 database migrations into a single migration for a clean schema ledger (#372)
    • Add a preflight check for missing API keys when building the installer (#334)
    • Isolate debug builds with a distin
  5. v1.6.12026-07-23 · 2026-07-23 02:16 UTC
    查看更新说明

    Fixes

    • Fix TMDB artist image fetching
    • Fix in-app issue reporter not working
    • Fix some inconsistencies with Simplified Chinese (zh-Hans) localization (#333) (Thanks @akkva)

    Full Changelog: https://github.com/kushalpandya/Petrichor/compare/v1.6.0...v1.6.1

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译


    Summary

    ✨ Features

    • Everything you'd expect from an offline music player!
    • Supports over 40 audio file formats. See Supported Formats for the complete list.
    • Map your music folders and browse your library in an organized view.
    • Stream Internet Radio and organize saved stations into collections.
    • Synced lyrics of a playing track when available, including ability to download missing lyrics.
    • Create, import or export playlists.
    • Smart playlists with conditional, negative, and regular-expression rules.
    • Manage the play queue interactively using drag and drop.
    • Browse music using folder view when needed.
    • Pin anything (almost!) to the sidebar for quick access to your favorite music.
    • Navigate easily: right-click a track to go to its album, artist, year, etc.
    • Native macOS integration with menubar and dock playback controls, plus dark mode support.
    • Miniplayer and immersive mode.
    • Gapless playback, Crossfade, ReplayGain volume normalization, Equalizer, Spatial Audio, and AirPlay 2 output.
    • Last.fm scrobbling support.
    • Shortcuts and Automation API support along with Siri.
    • Works well with large libraries containing thousands of songs.

    💡 Tip: Petrichor relies heavily on tracks having good metadata for all its features to work well.

    🎵 Supported Formats

    Petrichor imports and plays the 46 file extensions below. The equivalent in-app list is generated from the playback engine itself, so it always reflects the build you are running.

    FormatExtensionsType
    MP3 / MPEG Audio.mp3 .mp1 .mp2 .mpa .m1a .m2a .mpegLossy
    AAC.aac .m4a .m4r .adts .latm .loas .xheLossy
    ALAC (Apple Lossless).alac .m4aLossless
    FLAC.flacLossless
    Ogg Vorbis.ogg .oga .ogxLossy
    Opus.opusLossy
    Speex.spxLossy
    WAV.wavLossless
    Wave64.w64Lossless
    AIFF.aiff .aifLossless
    APE (Monkey's Audio).apeLossless
    WavPack.wvLossless
    TTA (True Audio).ttaLossless
    TAK.takLossless
    Shorten.shnLossless
    Musepack.mpcLossy
    DSD.dsf .dffLossless
    WMA.wmaLossy †
    AC-3.ac3Lossy
    E-AC-3.eac3 .ec3Lossy
    DTS.dtsLossy
    AMR / AMR-WB.amr .awbLossy
    CAF (Core Audio Format).caf .caffContainer1
    Matroska Audio.mkaContainer1
    AU / NeXT.au .sndLossless
    Sound Designer II.sd2Lossless

    1 Containers hold whichever codec was used, and WMA exists in both lossy and lossless variants. Petrichor reads the actual encoding from the file rather than guessing from the extension, so the Lossless badge on a track is always accurate.

    Not supported: DRM-protected files (.m4p, .aa, .aax), audiobook containers (.m4b), tracker modules (.mod, .it, .s3m, .xm), RealAudio (.ra, .ram), and MIDI (.mid, .midi).

    Starting 1.7, Petrichor no longer supports or imports the tracker-module formats MOD, IT, S3M, and XM that were supported in 1.6.x.

    ⌛ Roadmap

    • Automatic in-app updates (✅ v1.0.0 )
    • Better file format support (eg; Opus & OGG) (✅ v.1.2.0)
    • Audio Equalizer (✅ v.1.2.0)
    • Miniplayer and full-screen modes (✅ v.1.6.0)
    • Smart playlists with user-configurable conditional filters (✅ v.1.6.0)
    • Spatial Audio & Gapless playback (✅ v.1.6.0)
    • Crossfading support (✅ v1.7.0)
    • AirPlay 2 casting support (✅ v1.7.0)
    • Internet Streaming Radio (✅ v1.7.0)
    • Natural language search and smart playlists using Apple Intelligence
    • Audio Enhancements via Apple Audio Units
    • Back-up and import of preferences and library data
    • ... and much more!

     Requirements

    • macOS 14 or later (Apple Silicon or Intel)

    ⚙️ Installation

    Manually
    • Go to Releases and download the latest .dmg.
    • Open the it and drag the app icon into the Applications folder.
    • In Applications, right-click Petrichor > Open.
    Homebrew
    brew install --cask petrichor
    

    🚀 Get Started

    Petrichor User Guide

    📷 Screenshots

    Refer to official website

    🔒 Privacy & Data Access

    • Petrichor is sandboxed and notarized by Apple.
    • It has two permissions on macOS as follows;
      • Read-write access
        • To read and write into user-selected files and folders, write access is only used for exporting M3U playlist files.
      • Network access
        • To check for and install app updates.
        • Fetch artist images and bios from online sources (disabled by default)
        • Download track lyrics from the internet (disabled by default)
        • Stream Internet Radio stations (disabled by default)
        • Last.fm scrobbling (disabled by default)
          • When enabled, app may ask to store your Last.fm session information in macOS Keychain, if you choose to allow it, macOS will ask for your user account password to store the information in Keychain.
          • App does not store your Last.fm username or password, you still have to provide it on Last.fm website that opens in browser during configuration, once done, app only receives a session key to scrobble track playbacks with your account.
        • Reporting issues from within the app (user opt-in only)
          • Users can report a problem from within the app by going to Menu > Help > Report a Problem..., this opens a form where user is asked to fill out a few details, including detailed app diagnostics, along with app's log file.
          • This is optional feature and uses network only when user manually initiates this action and sends the information over the internet to app's backend server securely.
    • It doesn't (and never will) have any analytics on how you use the app.
    • It never changes your audio files or folder structure in any way.
    • Your library data remains offline always.

    🏗️ Development

    Motivation

    I have a large collection of music files that I’ve gathered over the years, and I missed having a good offline music player on macOS. I've used several free and paid options but I missed the simplicity and features commonly found in streaming apps; so I built Petrichor to scratch that itch and learn Swift and macOS app development along the way!

    Implementation Overview

    • Built with Swift and SwiftUI with some parts in AppKit for the best macOS integration.
    • Once folders containing music files are added, the app scans them, extracts required metadata, and populates the SQLite database.
    • The app does not alter your music files, it only reads from the directories you add.
    • Tracks searching is handled by SQLite FTS5.
    • Playback and metadata reading are handled by CrescendoKit, which uses FFmpeg and TagLib for broad format support.
    erDiagram
        folders {
            INTEGER id PK "AUTO_INCREMENT"
            TEXT name "NOT NULL"
            TEXT path "NOT NULL UNIQUE"
            INTEGER track_count "NOT NULL DEFAULT 0"
            DATETIME date_added "NOT NULL"
            DATETIME date_updated "NOT NULL"
            BLOB bookmark_data "Security-scoped bookmark"
            TEXT shasum_hash "Folder content hash"
        }
    
        artists {
            INTEGER id PK "AUTO_INCREMENT"
            TEXT name "NOT NULL"
            TEXT normalized_name "NOT NULL UNIQUE"
            TEXT sort_name
            BLOB artwork_data
            TEXT bio
            TEXT bio_source
            DATETIME bio_updated_at
            TEXT image_url
            TEXT image_source
            DATETIME image_updated_at
            TEXT discogs_id
            TEXT musicbrainz_id
            TEXT spotify_id
            TEXT apple_music_id
            TEXT country
            INTEGER formed_year
            INTEGER disbanded_year
            TEXT genres "JSON array"
            TEXT websites "JSON array"
            TEXT members "JSON array"
            INTEGER total_tracks "NOT NULL DEFAULT 0 CHECK >= 0"
            INTEGER total_albums "NOT NULL DEFAULT 0 CHECK >= 0"
            DATETIME created_at "NOT NULL"
            DATETIME updated_at "NOT NULL"
        }
    
        albums {
            INTEGER id PK "AUTO_INCREMENT"
            TEXT title "NOT NULL"
            TEXT normalized_title "NOT NULL"
            TEXT sort_title
            BLOB artwork_data
            TEXT release_date
            INTEGER release_year "CHECK 1900-2100"
            TEXT album_type
            INTEGER total_tracks "CHECK >= 0"
            INTEGER total_discs "CHECK >= 0"
            TEXT description
            TEXT review
            TEXT review_source
            TEXT cover_art_url
            TEXT thumbnail_url
            TEXT discogs_id
            TEXT musicbrainz_id
            TEXT spotify_id
            TEXT apple_music_id
            TEXT label
            TEXT catalog_number
            TEXT barcode
            TEXT genres "JSON array"
            DATETIME created_at "NOT NULL"
            DATETIME updated_at "NOT NULL"
        }
    
        album_artists {
            INTEGER album_id FK "NOT NULL"
            INTEGER artist_id FK "NOT NULL"
            TEXT role "NOT NULL DEFAULT 'primary'"
            INTEGER position "NOT NULL DEFAULT 0"
        }
    
        genres {
            INTEGER id PK "AUTO_INCREMENT"
            TEXT name "NOT NULL UNIQUE"
        }
    
        tracks {
            INTEGER id PK "AUTO_INCREMENT"
            INTEGER folder_id FK "NOT NULL"
            INTEGER album_id FK
            TEXT path "NOT NULL UNIQUE"
            TEXT filename "NOT NULL"
            TEXT title
            TEXT artist
            TEXT album
            TEXT composer
            TEXT genre
            TEXT year
            REAL duration "CHECK >= 0"
            TEXT format
            INTEGER file_size
            DATETIME date_added "NOT NULL"
            DATETIME date_modified
            BLOB track_artwork_data
            BOOLEAN is_favorite "NOT NULL DEFAULT false"
            INTEGER play_count "NOT NULL DEFAULT 0"
            DATETIME last_played_date
            BOOLEAN is_duplicate "NOT NULL DEFAULT false"
            INTEGER primary_track_id FK
            TEXT duplicate_group_id
            TEXT album_artist
            INTEGER track_number
            INTEGER total_tracks
            INTEGER disc_number
            INTEGER total_discs
            INTEGER rating "CHECK 0-5"
            BOOLEAN compilation "DEFAULT false"
            TEXT release_date
            TEXT original_release_date
            INTEGER bpm
            TEXT media_type "Music/Audiobook/Podcast"
            INTEGER bitrate "CHECK > 0"
            INTEGER sample_rate
            INTEGER channels "1=mono, 2=stereo"
            TEXT codec
            INTEGER bit_depth
            TEXT sort_title
            TEXT sort_artist
            TEXT sort_album
            TEXT sort_album_artist
            TEXT extended_metadata "JSON"
            BOOLEAN lossless
        }
    
        playlists {
            TEXT id PK "UUID"
            TEXT name "NOT NULL"
            TEXT type "NOT NULL (regular/smart)"
            BOOLEAN is_user_editable "NOT NULL"
            BOOLEAN is_content_editable "NOT NULL"
            DATETIME date_created "NOT NULL"
            DATETIME date_modified "NOT NULL"
            BLOB cover_artwork_data
            TEXT smart_criteria "JSON"
            INTEGER sort_order "NOT NULL DEFAULT 0"
        }
    
        playlist_tracks {
            TEXT playlist_id FK "NOT NULL"
            INTEGER track_id FK "NOT NULL"
            INTEGER position "NOT NULL"
            DATETIME date_added "NOT NULL"
        }
    
        track_artists {
            INTEGER track_id FK "NOT NULL"
            INTEGER artist_id FK "NOT NULL"
            TEXT role "NOT NULL DEFAULT 'artist'"
            INTEGER position "NOT NULL DEFAULT 0"
        }
    
        track_genres {
            INTEGER track_id FK "NOT NULL"
            INTEGER genre_id FK "NOT NULL"
        }
    
        pinned_items {
            INTEGER id PK "AUTO_INCREMENT"
            TEXT item_type "NOT NULL (library/playlist)"
            TEXT filter_type "For library items"
            TEXT filter_value "Artist/album name"
            TEXT entity_id "UUID for entities"
            INTEGER artist_id "Database ID"
            INTEGER album_id "Database ID"
            TEXT playlist_id "For playlist items"
            TEXT display_name "NOT NULL"
            TEXT subtitle "For albums"
            INTEGER sort_order "NOT NULL DEFAULT 0"
            DATETIME date_added "NOT NULL"
        }
    
        artist_aliases {
            TEXT normalized_alias PK
            TEXT display_name "NOT NULL"
            INTEGER canonical_artist_id FK "NOT NULL"
            DATETIME created_at "NOT NULL"
        }
    
        album_aliases {
            TEXT normalized_key PK
            TEXT display_title "NOT NULL"
            INTEGER canonical_album_id FK "NOT NULL"
            DATETIME created_at "NOT NULL"
        }
    
        background_migrations {
            TEXT identifier PK
            DATETIME completed_at
            TEXT progress
            BOOLEAN resumable "DEFAULT true"
        }
    
        tracks_fts {
            INTEGER track_id "NOT INDEXED"
            TEXT title
            TEXT artist
            TEXT album
            TEXT album_artist
            TEXT composer
            TEXT genre
            TEXT year
        }
    
        folders ||--o{ tracks : contains
        albums ||--o{ album_artists : "has artists"
        artists ||--o{ album_artists : "appears on"
        albums ||--o{ tracks : contains
        artists ||--o{ track_artists : "appears in"
        tracks ||--o{ track_artists : "has artists"
        tracks ||--o{ tracks : "duplicate of"
        genres ||--o{ track_genres : "categorizes"
        tracks ||--o{ track_genres : "has genres"
        playlists ||--o{ playlist_tracks : contains
        tracks ||--o{ playlist_tracks : "appears in"
        tracks ||--|| tracks_fts : "searchable in"
        artists ||--o{ artist_aliases : "merged into"
        albums ||--o{ album_aliases : "merged into"
    

    Credits

    Petrichor wouldn't be possible without following open source projects!

    Development Setup

    • Make sure you’re running macOS 14 or later.
    • Install Xcode.
    • Clone the repository and open Petrichor.xcodeproj
    Build & Release

    You can build your own .dmg installer using the build-installer.sh script, although it requires you to have a paid Apple Developer account to notarize the compiled binary and installer, you can use --bypass-notary option if you don't want to notarize. To use the script, make sure you have following tools installed along with Xcode;

    💖 Sponsors

    Thank you to all the sponsors for supporting Petrichor's development!

    📝 License

    • Petrichor is licensed under MIT
    • GRDB.swift and Sparkle are licensed under MIT.
    • The playback engine and metadata reader come from CrescendoKit (proprietary, binary distribution), which dynamically links FFmpeg (LGPL-2.1-or-later) and statically embeds TagLib (MPL-1.1).

    For complete third-party license information, see ACKNOWLEDGEMENTS.md