Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 48 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ deb_depends = ["libasound2", "libpulse0", "libgtk-3-0"]
cpal = "0.15"
eframe = "0.31.1"
egui_extras = { version = "0.31.1", features = ["all_loaders", "image"] }
id3 = "1.16.2"
itertools = "0.12"
lazy_static = "1.4.0"
rayon = "1.10"
Expand All @@ -51,6 +50,7 @@ libpulse-simple-binding = { version = "2.27.1", optional = true }
urlencoding = "2.1"
ureq = "2.9"
uuid = { version = "1.23.0", default-features = false, features = ["v4"] }
lofty = "0.21.0"

[features]
default = []
Expand Down
55 changes: 55 additions & 0 deletions build_mac_app.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/bash
set -e

APP_NAME="BirdPlayer"
APP_DIR="target/release/$APP_NAME.app"
CONTENTS_DIR="$APP_DIR/Contents"
MACOS_DIR="$CONTENTS_DIR/MacOS"
RESOURCES_DIR="$CONTENTS_DIR/Resources"

echo "Building release binary..."
cargo build --release

echo "Creating app bundle structure..."
mkdir -p "$MACOS_DIR"
mkdir -p "$RESOURCES_DIR"

echo "Copying binary..."
cp target/release/bird-player "$MACOS_DIR/"

echo "Copying icon..."
if [ -f "assets/icons/icon.icns" ]; then
cp assets/icons/icon.icns "$RESOURCES_DIR/"
fi

echo "Creating Info.plist..."
cat << PLIST > "$CONTENTS_DIR/Info.plist"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>bird-player</string>
<key>CFBundleIconFile</key>
<string>icon.icns</string>
<key>CFBundleIdentifier</key>
<string>com.retric.birdplayer</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>BirdPlayer</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.2.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>
PLIST

echo "App bundle created at $APP_DIR"
24 changes: 15 additions & 9 deletions src/app/components/library_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,21 @@ impl AppComponent for LibraryComponent {
{
if let Some(new_path) = rfd::FileDialog::new().pick_folder() {
// Add the path to the library
ctx.library.add_path(new_path);

// Get the last added path and import it
if let Some(newest_path) = ctx.library.paths().last() {
if newest_path.status()
== crate::app::library::LibraryPathStatus::NotImported
{
ctx.import_library_paths(newest_path);
}
let path_exists = !ctx.library.add_path(new_path.clone());

// If it existed, find it and rescan. If new, import the bottom-most path.
let path_to_import = if path_exists {
ctx.library
.paths()
.iter()
.find(|p| *p.path() == new_path)
.cloned()
} else {
ctx.library.paths().last().cloned()
};

if let Some(p) = path_to_import {
ctx.import_library_paths(&p);
}
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/app/components/playlist_table/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,25 @@ impl<'a> PlaylistTableService<'a> {
}
}

#[allow(dead_code)]
pub(crate) fn update_cover(&mut self, idx: usize, image_path: std::path::PathBuf) {
let Some(mut track) = self
.playlist()
.and_then(|playlist| playlist.tracks.get(idx).cloned())
else {
return;
};

if self.ctx.update_track_cover(&mut track, &image_path) {
if let Some(slot) = self
.playlist_mut()
.and_then(|playlist| playlist.tracks.get_mut(idx))
{
*slot = track;
}
}
}

pub(crate) fn play_track(&mut self, idx: usize) {
let Some(track) = self
.playlist()
Expand Down
24 changes: 15 additions & 9 deletions src/app/components/window_chrome.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,21 @@ impl AppComponent for WindowChrome {
if ui.button(t("open")).clicked() {
if let Some(new_path) = rfd::FileDialog::new().pick_folder() {
// Add the path to the library
ctx.library.add_path(new_path);

// Get the last added path and import it
if let Some(newest_path) = ctx.library.paths().last() {
if newest_path.status()
== crate::app::library::LibraryPathStatus::NotImported
{
ctx.import_library_paths(newest_path);
}
let path_exists = !ctx.library.add_path(new_path.clone());

// If it existed, find it and rescan. If new, import the bottom-most path.
let path_to_import = if path_exists {
ctx.library
.paths()
.iter()
.find(|p| *p.path() == new_path)
.cloned()
} else {
ctx.library.paths().last().cloned()
};

if let Some(p) = path_to_import {
ctx.import_library_paths(&p);
}
}
ui.close_menu();
Expand Down
23 changes: 23 additions & 0 deletions src/app/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,29 @@ impl App {
success
}

#[allow(dead_code)]
pub fn update_track_cover(
&mut self,
track: &mut LibraryItem,
image_path: &std::path::PathBuf,
) -> bool {
let db_conn = self.db().connection();

let success = LibraryService::update_track_cover(
track,
image_path,
&mut self.library,
&mut self.playlists,
&db_conn,
);

if success {
self.save_state();
}

success
}

// Add these new methods for language handling
pub fn set_language(&mut self, lang: i18n::Language) {
self.app_settings.current_language = lang;
Expand Down
2 changes: 1 addition & 1 deletion src/app/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub struct Database {

impl Database {
// The current schema version - increment this when making schema changes
const SCHEMA_VERSION: i32 = 3;
const SCHEMA_VERSION: i32 = 4;

pub fn new() -> Result<Self> {
// Get the app's configuration directory
Expand Down
22 changes: 22 additions & 0 deletions src/app/services/library_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,26 @@ impl LibraryService {
LibraryCommand::AddPathId(path_id) => library.set_path_to_imported(path_id),
}
}

/// Update track cover image
#[allow(dead_code)]
pub fn update_track_cover(
track: &mut LibraryItem,
image_path: &std::path::PathBuf,
library: &mut Library,
_playlists: &mut [Playlist],
db_conn: &Arc<Mutex<rusqlite::Connection>>,
) -> bool {
let success = MetadataEditor::update_track_cover(track, image_path);

// When the cover changes, we simply reload the library from the database
// and playlists just like `update_track_metadata` does because the cover
// path needs to be refreshed from the newly extracted data
if success {
if let Ok(updated_library) = Library::load_from_db(db_conn) {
*library = updated_library;
}
}
success
}
}
Loading
Loading