mirror of
https://github.com/danbee/persephone
synced 2025-03-04 08:39:11 +00:00
Get currently playing art using Kingfisher
This commit is contained in:
parent
d407f1e5f9
commit
4322a25b8b
@ -29,9 +29,6 @@
|
|||||||
E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5306223C019100173814 /* MPDClient+Status.swift */; };
|
E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5306223C019100173814 /* MPDClient+Status.swift */; };
|
||||||
E41E5309223C020400173814 /* MPDClient+Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5308223C020400173814 /* MPDClient+Command.swift */; };
|
E41E5309223C020400173814 /* MPDClient+Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5308223C020400173814 /* MPDClient+Command.swift */; };
|
||||||
E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530A223C033700173814 /* MPDClient+Album.swift */; };
|
E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530A223C033700173814 /* MPDClient+Album.swift */; };
|
||||||
E41E530E223EF4CF00173814 /* CoverArtService+Caching.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530D223EF4CF00173814 /* CoverArtService+Caching.swift */; };
|
|
||||||
E41E5310223EF6CE00173814 /* CoverArtService+Remote.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530F223EF6CE00173814 /* CoverArtService+Remote.swift */; };
|
|
||||||
E41E5312223EF74A00173814 /* CoverArtService+Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5311223EF74A00173814 /* CoverArtService+Filesystem.swift */; };
|
|
||||||
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */; };
|
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */; };
|
||||||
E4235640228623D2001216D6 /* QueueSongTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E423563F228623D2001216D6 /* QueueSongTitleView.swift */; };
|
E4235640228623D2001216D6 /* QueueSongTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E423563F228623D2001216D6 /* QueueSongTitleView.swift */; };
|
||||||
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42410B52241B956005ED6DF /* MPDClient+Database.swift */; };
|
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42410B52241B956005ED6DF /* MPDClient+Database.swift */; };
|
||||||
@ -85,7 +82,6 @@
|
|||||||
E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* MPDStatus.swift */; };
|
E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* MPDStatus.swift */; };
|
||||||
E4A83BEF2221F8CF0098FED6 /* CoverArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */; };
|
E4A83BEF2221F8CF0098FED6 /* CoverArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */; };
|
||||||
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; };
|
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; };
|
||||||
E4A83BF4222207D50098FED6 /* CoverArtService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF3222207D50098FED6 /* CoverArtService.swift */; };
|
|
||||||
E4B11B53226928F20075461B /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B52226928F20075461B /* AppState.swift */; };
|
E4B11B53226928F20075461B /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B52226928F20075461B /* AppState.swift */; };
|
||||||
E4B11B61226A4C000075461B /* PlayerReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B60226A4BFF0075461B /* PlayerReducer.swift */; };
|
E4B11B61226A4C000075461B /* PlayerReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B60226A4BFF0075461B /* PlayerReducer.swift */; };
|
||||||
E4B11B63226A4C510075461B /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B62226A4C510075461B /* AppReducer.swift */; };
|
E4B11B63226A4C510075461B /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B62226A4C510075461B /* AppReducer.swift */; };
|
||||||
@ -234,9 +230,6 @@
|
|||||||
E41E5306223C019100173814 /* MPDClient+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Status.swift"; sourceTree = "<group>"; };
|
E41E5306223C019100173814 /* MPDClient+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Status.swift"; sourceTree = "<group>"; };
|
||||||
E41E5308223C020400173814 /* MPDClient+Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Command.swift"; sourceTree = "<group>"; };
|
E41E5308223C020400173814 /* MPDClient+Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Command.swift"; sourceTree = "<group>"; };
|
||||||
E41E530A223C033700173814 /* MPDClient+Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Album.swift"; sourceTree = "<group>"; };
|
E41E530A223C033700173814 /* MPDClient+Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Album.swift"; sourceTree = "<group>"; };
|
||||||
E41E530D223EF4CF00173814 /* CoverArtService+Caching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoverArtService+Caching.swift"; sourceTree = "<group>"; };
|
|
||||||
E41E530F223EF6CE00173814 /* CoverArtService+Remote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoverArtService+Remote.swift"; sourceTree = "<group>"; };
|
|
||||||
E41E5311223EF74A00173814 /* CoverArtService+Filesystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoverArtService+Filesystem.swift"; sourceTree = "<group>"; };
|
|
||||||
E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPrefsViewController.swift; sourceTree = "<group>"; };
|
E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPrefsViewController.swift; sourceTree = "<group>"; };
|
||||||
E423563F228623D2001216D6 /* QueueSongTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueSongTitleView.swift; sourceTree = "<group>"; };
|
E423563F228623D2001216D6 /* QueueSongTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueSongTitleView.swift; sourceTree = "<group>"; };
|
||||||
E42410B52241B956005ED6DF /* MPDClient+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Database.swift"; sourceTree = "<group>"; };
|
E42410B52241B956005ED6DF /* MPDClient+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Database.swift"; sourceTree = "<group>"; };
|
||||||
@ -284,7 +277,6 @@
|
|||||||
E4A642D922090CBE00067D21 /* MPDStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDStatus.swift; sourceTree = "<group>"; };
|
E4A642D922090CBE00067D21 /* MPDStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDStatus.swift; sourceTree = "<group>"; };
|
||||||
E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtPrefsController.swift; sourceTree = "<group>"; };
|
E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtPrefsController.swift; sourceTree = "<group>"; };
|
||||||
E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; };
|
E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; };
|
||||||
E4A83BF3222207D50098FED6 /* CoverArtService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtService.swift; sourceTree = "<group>"; };
|
|
||||||
E4B11B52226928F20075461B /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
|
E4B11B52226928F20075461B /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
|
||||||
E4B11B60226A4BFF0075461B /* PlayerReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerReducer.swift; sourceTree = "<group>"; };
|
E4B11B60226A4BFF0075461B /* PlayerReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerReducer.swift; sourceTree = "<group>"; };
|
||||||
E4B11B62226A4C510075461B /* AppReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducer.swift; sourceTree = "<group>"; };
|
E4B11B62226A4C510075461B /* AppReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducer.swift; sourceTree = "<group>"; };
|
||||||
@ -514,16 +506,6 @@
|
|||||||
path = mpd;
|
path = mpd;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E41E530C223EF4BA00173814 /* Extensions */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E41E530D223EF4CF00173814 /* CoverArtService+Caching.swift */,
|
|
||||||
E41E5311223EF74A00173814 /* CoverArtService+Filesystem.swift */,
|
|
||||||
E41E530F223EF6CE00173814 /* CoverArtService+Remote.swift */,
|
|
||||||
);
|
|
||||||
path = Extensions;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E442CCC42347D5B900004E0C /* Components */ = {
|
E442CCC42347D5B900004E0C /* Components */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -642,8 +624,6 @@
|
|||||||
E4A83BF2222207BE0098FED6 /* Services */ = {
|
E4A83BF2222207BE0098FED6 /* Services */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E41E530C223EF4BA00173814 /* Extensions */,
|
|
||||||
E4A83BF3222207D50098FED6 /* CoverArtService.swift */,
|
|
||||||
E439109722640213002982E9 /* SongNotifierService.swift */,
|
E439109722640213002982E9 /* SongNotifierService.swift */,
|
||||||
);
|
);
|
||||||
path = Services;
|
path = Services;
|
||||||
@ -949,7 +929,6 @@
|
|||||||
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */,
|
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */,
|
||||||
E4B11B63226A4C510075461B /* AppReducer.swift in Sources */,
|
E4B11B63226A4C510075461B /* AppReducer.swift in Sources */,
|
||||||
E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */,
|
E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */,
|
||||||
E41E5310223EF6CE00173814 /* CoverArtService+Remote.swift in Sources */,
|
|
||||||
E442CCCD2347E73C00004E0C /* Artist.swift in Sources */,
|
E442CCCD2347E73C00004E0C /* Artist.swift in Sources */,
|
||||||
E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */,
|
E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */,
|
||||||
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */,
|
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */,
|
||||||
@ -1005,18 +984,15 @@
|
|||||||
E4120D6C22AD8139004CB1F8 /* QueueView.swift in Sources */,
|
E4120D6C22AD8139004CB1F8 /* QueueView.swift in Sources */,
|
||||||
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */,
|
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */,
|
||||||
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
|
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
|
||||||
E4A83BF4222207D50098FED6 /* CoverArtService.swift in Sources */,
|
|
||||||
E47E2FD5222071FD00F747E6 /* AlbumViewItem.swift in Sources */,
|
E47E2FD5222071FD00F747E6 /* AlbumViewItem.swift in Sources */,
|
||||||
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
|
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
|
||||||
E4B11B66226A4F830075461B /* PlayerState.swift in Sources */,
|
E4B11B66226A4F830075461B /* PlayerState.swift in Sources */,
|
||||||
E4B11BBE2275EDAA0075461B /* PlayerActions.swift in Sources */,
|
E4B11BBE2275EDAA0075461B /* PlayerActions.swift in Sources */,
|
||||||
E4F26F7723411AE300D45FF9 /* ArtistListActions.swift in Sources */,
|
E4F26F7723411AE300D45FF9 /* ArtistListActions.swift in Sources */,
|
||||||
E4FF71942276043A00D4C412 /* MPDServer.swift in Sources */,
|
E4FF71942276043A00D4C412 /* MPDServer.swift in Sources */,
|
||||||
E41E530E223EF4CF00173814 /* CoverArtService+Caching.swift in Sources */,
|
|
||||||
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,
|
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,
|
||||||
E440519C227BAF2E0090CD6F /* UIActions.swift in Sources */,
|
E440519C227BAF2E0090CD6F /* UIActions.swift in Sources */,
|
||||||
E45878382296173C00586A1C /* AlbumDetailSongRowView.swift in Sources */,
|
E45878382296173C00586A1C /* AlbumDetailSongRowView.swift in Sources */,
|
||||||
E41E5312223EF74A00173814 /* CoverArtService+Filesystem.swift in Sources */,
|
|
||||||
E41E5301223BF99300173814 /* MPDClient+Queue.swift in Sources */,
|
E41E5301223BF99300173814 /* MPDClient+Queue.swift in Sources */,
|
||||||
E4EB237B220F7CF1008C70C0 /* MPDAlbum.swift in Sources */,
|
E4EB237B220F7CF1008C70C0 /* MPDAlbum.swift in Sources */,
|
||||||
E41E5303223BF9C300173814 /* MPDClient+Idle.swift in Sources */,
|
E41E5303223BF9C300173814 /* MPDClient+Idle.swift in Sources */,
|
||||||
|
|||||||
@ -13,18 +13,6 @@ class AlbumViewItem: NSCollectionViewItem {
|
|||||||
var observer: NSKeyValueObservation?
|
var observer: NSKeyValueObservation?
|
||||||
var album: Album?
|
var album: Album?
|
||||||
|
|
||||||
var coverArtFilenames: [String] {
|
|
||||||
return [
|
|
||||||
"folder.jpg",
|
|
||||||
"cover.jpg",
|
|
||||||
"\(album?.artist ?? "") - \(album?.title ?? "").jpg"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
var musicDir: String {
|
|
||||||
return App.store.state.preferencesState.expandedMpdLibraryDir
|
|
||||||
}
|
|
||||||
|
|
||||||
override var isSelected: Bool {
|
override var isSelected: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
setAppearance(selected: isSelected)
|
setAppearance(selected: isSelected)
|
||||||
@ -67,7 +55,7 @@ class AlbumViewItem: NSCollectionViewItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setAlbumCover(_ album: Album) {
|
func setAlbumCover(_ album: Album) {
|
||||||
guard let imagePath = fileSystemArtworkFilePath() else { return }
|
guard let imagePath = album.coverArtFilePath else { return }
|
||||||
|
|
||||||
let imageURL = URL(fileURLWithPath: imagePath)
|
let imageURL = URL(fileURLWithPath: imagePath)
|
||||||
let provider = LocalFileImageDataProvider(fileURL: imageURL)
|
let provider = LocalFileImageDataProvider(fileURL: imageURL)
|
||||||
@ -81,15 +69,6 @@ class AlbumViewItem: NSCollectionViewItem {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileSystemArtworkFilePath() -> String? {
|
|
||||||
return self.coverArtFilenames
|
|
||||||
.lazy
|
|
||||||
.map { "\(self.musicDir)/\(self.album?.mpdAlbum.path ?? "")/\($0)" }
|
|
||||||
.first {
|
|
||||||
FileManager.default.fileExists(atPath: $0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setAppearance(selected isSelected: Bool) {
|
func setAppearance(selected isSelected: Bool) {
|
||||||
guard let viewLayer = albumCoverView.layer,
|
guard let viewLayer = albumCoverView.layer,
|
||||||
let boxLayer = albumCoverBox.layer
|
let boxLayer = albumCoverBox.layer
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import AppKit
|
import AppKit
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
class AlbumDetailView: NSViewController {
|
class AlbumDetailView: NSViewController {
|
||||||
var observer: NSKeyValueObservation?
|
var observer: NSKeyValueObservation?
|
||||||
@ -130,15 +131,18 @@ class AlbumDetailView: NSViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getBigCoverArt(song: Song, album: Album) {
|
func getBigCoverArt(song: Song, album: Album) {
|
||||||
let coverArtService = CoverArtService(path: song.mpdSong.path, album: album)
|
guard let imagePath = album.coverArtFilePath else { return }
|
||||||
|
|
||||||
coverArtService.fetchBigCoverArt()
|
let imageURL = URL(fileURLWithPath: imagePath)
|
||||||
.done(on: DispatchQueue.main) { [weak self] image in
|
let provider = LocalFileImageDataProvider(fileURL: imageURL)
|
||||||
if let image = image {
|
albumCoverView.kf.setImage(
|
||||||
self?.albumCoverView.image = image
|
with: .provider(provider),
|
||||||
}
|
placeholder: NSImage.defaultCoverArt,
|
||||||
}
|
options: [
|
||||||
.cauterize()
|
.processor(DownsamplingImageProcessor(size: NSSize(width: 500, height: 500))),
|
||||||
|
.scaleFactor(2),
|
||||||
|
]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setAppearance() {
|
func setAppearance() {
|
||||||
|
|||||||
@ -8,23 +8,39 @@
|
|||||||
|
|
||||||
import AppKit
|
import AppKit
|
||||||
import ReSwift
|
import ReSwift
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
class CurrentCoverArtView: NSImageView {
|
class CurrentCoverArtView: NSImageView {
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
super.init(coder: coder)
|
super.init(coder: coder)
|
||||||
|
|
||||||
App.store.subscribe(self) {
|
App.store.subscribe(self) {
|
||||||
$0.select { $0.playerState.currentArtwork }
|
$0.select { $0.playerState.currentSong }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setAlbumImage(_ album: Album) {
|
||||||
|
guard let imagePath = album.coverArtFilePath else { return }
|
||||||
|
|
||||||
|
let imageURL = URL(fileURLWithPath: imagePath)
|
||||||
|
let provider = LocalFileImageDataProvider(fileURL: imageURL)
|
||||||
|
self.kf.setImage(
|
||||||
|
with: .provider(provider),
|
||||||
|
placeholder: NSImage.defaultCoverArt,
|
||||||
|
options: [
|
||||||
|
.processor(DownsamplingImageProcessor(size: NSSize(width: 500, height: 500))),
|
||||||
|
.scaleFactor(2),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CurrentCoverArtView: StoreSubscriber {
|
extension CurrentCoverArtView: StoreSubscriber {
|
||||||
typealias StoreSubscriberStateType = NSImage?
|
typealias StoreSubscriberStateType = Song?
|
||||||
|
|
||||||
func newState(state: NSImage?) {
|
func newState(state: Song?) {
|
||||||
if let coverArt = state {
|
if let song = state {
|
||||||
image = coverArt
|
setAlbumImage(song.album)
|
||||||
} else {
|
} else {
|
||||||
image = .defaultCoverArt
|
image = .defaultCoverArt
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,15 +21,15 @@ class UserNotificationsController {
|
|||||||
let status = App.mpdClient.status,
|
let status = App.mpdClient.status,
|
||||||
status.state == .playing
|
status.state == .playing
|
||||||
else { return }
|
else { return }
|
||||||
|
//
|
||||||
let coverArtService = CoverArtService(path: currentSong.mpdSong.path, album: currentSong.album)
|
// let coverArtService = CoverArtService(path: currentSong.mpdSong.path, album: currentSong.album)
|
||||||
|
//
|
||||||
coverArtService.fetchBigCoverArt()
|
// coverArtService.fetchBigCoverArt()
|
||||||
.done() {
|
// .done() {
|
||||||
SongNotifierService(song: currentSong, image: $0)
|
// SongNotifierService(song: currentSong, image: $0)
|
||||||
.deliver()
|
// .deliver()
|
||||||
}
|
// }
|
||||||
.cauterize()
|
// .cauterize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -162,7 +162,6 @@ class WindowController: NSWindowController {
|
|||||||
|
|
||||||
@IBAction func handleSearchQuery(_ sender: NSSearchField) {
|
@IBAction func handleSearchQuery(_ sender: NSSearchField) {
|
||||||
//App.store.dispatch(SetSearchQuery(searchQuery: sender.stringValue))
|
//App.store.dispatch(SetSearchQuery(searchQuery: sender.stringValue))
|
||||||
CoverArtService.coverArtQueue
|
|
||||||
App.mpdClient.fetchAlbums(filter: sender.stringValue)
|
App.mpdClient.fetchAlbums(filter: sender.stringValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,26 @@ struct Album {
|
|||||||
var hash: String {
|
var hash: String {
|
||||||
return "\(title) - \(artist)".sha1()
|
return "\(title) - \(artist)".sha1()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var coverArtFilenames: [String] {
|
||||||
|
return [
|
||||||
|
"folder.jpg",
|
||||||
|
"cover.jpg",
|
||||||
|
"\(artist) - \(title ).jpg"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
var coverArtFilePath: String? {
|
||||||
|
let musicDir = App.store.state.preferencesState.expandedMpdLibraryDir
|
||||||
|
guard let albumPath = mpdAlbum.path else { return nil }
|
||||||
|
|
||||||
|
return coverArtFilenames
|
||||||
|
.lazy
|
||||||
|
.map { "\(musicDir)/\(albumPath)/\($0)" }
|
||||||
|
.first {
|
||||||
|
FileManager.default.fileExists(atPath: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Album: Equatable {
|
extension Album: Equatable {
|
||||||
|
|||||||
@ -1,70 +0,0 @@
|
|||||||
//
|
|
||||||
// CoverArtService.swift
|
|
||||||
// Persephone
|
|
||||||
//
|
|
||||||
// Created by Daniel Barber on 2019/2/23.
|
|
||||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import AppKit
|
|
||||||
import PromiseKit
|
|
||||||
|
|
||||||
class CoverArtService {
|
|
||||||
let path: String
|
|
||||||
let album: Album
|
|
||||||
|
|
||||||
let cachedArtworkSize = 180
|
|
||||||
let cachedArtworkQuality: CGFloat = 0.5
|
|
||||||
|
|
||||||
let bigArtworkSize = 600
|
|
||||||
|
|
||||||
var session = URLSession(configuration: .default)
|
|
||||||
static let coverArtQueue = DispatchQueue(label: "coverArtQueue", qos: .utility)
|
|
||||||
|
|
||||||
init(path: String, album: Album) {
|
|
||||||
self.path = path
|
|
||||||
self.album = album
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchBigCoverArt() -> Promise<NSImage?> {
|
|
||||||
return firstly {
|
|
||||||
self.getArtworkFromFilesystem()
|
|
||||||
}.then { (image: NSImage?) -> Promise<NSImage?> in
|
|
||||||
image.map(Promise.value) ?? self.getRemoteArtwork()
|
|
||||||
}.recover { (_) -> Guarantee<NSImage?> in
|
|
||||||
return .value(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchCoverArt() -> Guarantee<NSImage?> {
|
|
||||||
return firstly {
|
|
||||||
self.getCachedArtwork()
|
|
||||||
}.then { (artwork: NSImage?) -> Promise<NSImage?> in
|
|
||||||
artwork.map(Promise.value) ?? self.getArtworkFromFilesystem()
|
|
||||||
}.then { (artwork: NSImage?) -> Promise<NSImage?> in
|
|
||||||
artwork.map(Promise.value) ?? self.getRemoteArtwork()
|
|
||||||
}.compactMap(on: CoverArtService.coverArtQueue) {
|
|
||||||
return self.sizeAndCacheImage($0).map(Optional.some)
|
|
||||||
}.recover { _ in
|
|
||||||
return .value(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sizeAndCacheImage(_ image: NSImage?) -> NSImage? {
|
|
||||||
switch image {
|
|
||||||
case nil:
|
|
||||||
self.cacheArtwork(data: Data())
|
|
||||||
return image
|
|
||||||
case let image:
|
|
||||||
if self.isArtworkCached() {
|
|
||||||
return image
|
|
||||||
} else {
|
|
||||||
let sizedImage = image?.toFitBox(
|
|
||||||
size: NSSize(width: self.cachedArtworkSize, height: self.cachedArtworkSize)
|
|
||||||
)
|
|
||||||
self.cacheArtwork(data: sizedImage?.jpegData(compressionQuality: self.cachedArtworkQuality))
|
|
||||||
return sizedImage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
//
|
|
||||||
// CoverArtService+Caching.swift
|
|
||||||
// Persephone
|
|
||||||
//
|
|
||||||
// Created by Daniel Barber on 2019/3/17.
|
|
||||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import AppKit
|
|
||||||
import PromiseKit
|
|
||||||
|
|
||||||
extension CoverArtService {
|
|
||||||
static let cacheDir = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent(Bundle.main.bundleIdentifier!)
|
|
||||||
|
|
||||||
func getCachedArtwork() -> Promise<NSImage?> {
|
|
||||||
return Promise { seal in
|
|
||||||
CoverArtService.coverArtQueue.async {
|
|
||||||
if self.isArtworkCached() {
|
|
||||||
let cacheFilePath = CoverArtService.cacheDir.appendingPathComponent(self.album.hash).path
|
|
||||||
let data = FileManager.default.contents(atPath: cacheFilePath)
|
|
||||||
let image = NSImage(data: data ?? Data()) ?? NSImage.defaultCoverArt
|
|
||||||
|
|
||||||
seal.fulfill(image)
|
|
||||||
} else {
|
|
||||||
seal.fulfill(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cacheArtwork(data: Data?) {
|
|
||||||
CoverArtService.coverArtQueue.async {
|
|
||||||
guard let bundleIdentifier = Bundle.main.bundleIdentifier,
|
|
||||||
let cacheDir = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
|
||||||
.appendingPathComponent(bundleIdentifier)
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
let cacheFilePath = cacheDir.appendingPathComponent(self.album.hash).path
|
|
||||||
|
|
||||||
if !self.isArtworkCached() {
|
|
||||||
FileManager.default.createFile(atPath: cacheFilePath, contents: data, attributes: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isArtworkCached() -> Bool {
|
|
||||||
let cacheFilePath = CoverArtService.cacheDir.appendingPathComponent(album.hash).path
|
|
||||||
|
|
||||||
return FileManager.default.fileExists(atPath: cacheFilePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
//
|
|
||||||
// CoverArtService+Filesystem.swift
|
|
||||||
// Persephone
|
|
||||||
//
|
|
||||||
// Created by Daniel Barber on 2019/3/17.
|
|
||||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import AppKit
|
|
||||||
import PromiseKit
|
|
||||||
|
|
||||||
extension CoverArtService {
|
|
||||||
var coverArtFilenames: [String] {
|
|
||||||
return [
|
|
||||||
"folder.jpg",
|
|
||||||
"cover.jpg",
|
|
||||||
"\(album.artist) - \(album.title).jpg"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
var musicDir: String {
|
|
||||||
return App.store.state.preferencesState.expandedMpdLibraryDir
|
|
||||||
}
|
|
||||||
|
|
||||||
func getArtworkFromFilesystem() -> Promise<NSImage?> {
|
|
||||||
return Promise { seal in
|
|
||||||
CoverArtService.coverArtQueue.async {
|
|
||||||
guard let artworkPath = self.fileSystemArtworkFilePath()
|
|
||||||
else { seal.fulfill(nil); return }
|
|
||||||
|
|
||||||
let image = self.tryImage(artworkPath)
|
|
||||||
|
|
||||||
seal.fulfill(image)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveArtworkToFilesystem(data: Data?) {
|
|
||||||
let artworkFileName = coverArtFilenames.first!
|
|
||||||
|
|
||||||
if self.fileSystemArtworkFilePath() == nil {
|
|
||||||
FileManager.default.createFile(
|
|
||||||
atPath: "\(self.musicDir)/\(self.path)/\(artworkFileName)",
|
|
||||||
contents: data,
|
|
||||||
attributes: nil
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileSystemArtworkFilePath() -> String? {
|
|
||||||
let musicDir = App.store.state.preferencesState.expandedMpdLibraryDir
|
|
||||||
|
|
||||||
return self.coverArtFilenames
|
|
||||||
.lazy
|
|
||||||
.map { "\(musicDir)/\(self.path)/\($0)" }
|
|
||||||
.first {
|
|
||||||
FileManager.default.fileExists(atPath: $0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tryImage(_ filePath: String) -> NSImage? {
|
|
||||||
guard let data = FileManager.default.contents(atPath: filePath),
|
|
||||||
let image = NSImage(data: data)
|
|
||||||
else { return nil }
|
|
||||||
|
|
||||||
return image
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
//
|
|
||||||
// CoverArtService+Remote.swift
|
|
||||||
// Persephone
|
|
||||||
//
|
|
||||||
// Created by Daniel Barber on 2019/3/17.
|
|
||||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import AppKit
|
|
||||||
import SwiftyJSON
|
|
||||||
import PromiseKit
|
|
||||||
import PMKFoundation
|
|
||||||
|
|
||||||
extension CoverArtService {
|
|
||||||
enum RemoteArtworkError: Error {
|
|
||||||
case noArtworkAvailable
|
|
||||||
case notConfigured
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRemoteArtwork() -> Promise<NSImage?> {
|
|
||||||
return Promise { seal in
|
|
||||||
if App.store.state.preferencesState .fetchMissingArtworkFromInternet {
|
|
||||||
CoverArtService.coverArtQueue.async {
|
|
||||||
let coverArtWorkItem = DispatchWorkItem {
|
|
||||||
self.getArtworkFromMusicBrainz().map(Optional.some).pipe(to: seal.resolve)
|
|
||||||
}
|
|
||||||
|
|
||||||
CoverArtQueue.shared.addToQueue(workItem: coverArtWorkItem)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw RemoteArtworkError.notConfigured
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getArtworkFromMusicBrainz() -> Promise<NSImage> {
|
|
||||||
var search = URLComponents(string: "https://musicbrainz.org/ws/2/release/")!
|
|
||||||
search.query = "query=artist:\(album.artist) AND release:\(album.title) AND country:US&limit=1&fmt=json"
|
|
||||||
|
|
||||||
return firstly {
|
|
||||||
URLSession.shared.dataTask(.promise, with: search.url!).validate()
|
|
||||||
}.compactMap {
|
|
||||||
JSON($0.data)
|
|
||||||
}.compactMap {
|
|
||||||
$0["releases"][0]["id"].string
|
|
||||||
}.compactMap {
|
|
||||||
URLComponents(string: "https://coverartarchive.org/release/\($0)/front-500")?.url
|
|
||||||
}.then { (url: URL?) -> Promise<(data: Data, response: URLResponse)> in
|
|
||||||
return URLSession.shared.dataTask(.promise, with: url!).validate()
|
|
||||||
}.compactMap {
|
|
||||||
NSImage(data: $0.data)?.toFitBox(
|
|
||||||
size: NSSize(width: self.cachedArtworkSize, height: self.cachedArtworkSize)
|
|
||||||
)
|
|
||||||
}.recover { error -> Promise<NSImage> in
|
|
||||||
if case PMKHTTPError.badStatusCode(404, _, _) = error {
|
|
||||||
throw RemoteArtworkError.noArtworkAvailable
|
|
||||||
} else {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -12,7 +12,6 @@ import ReSwift
|
|||||||
struct PlayerState: StateType {
|
struct PlayerState: StateType {
|
||||||
var status: MPDClient.MPDStatus?
|
var status: MPDClient.MPDStatus?
|
||||||
var currentSong: Song?
|
var currentSong: Song?
|
||||||
var currentArtwork: NSImage?
|
|
||||||
|
|
||||||
var state: MPDClient.MPDStatus.State?
|
var state: MPDClient.MPDStatus.State?
|
||||||
var shuffleState: Bool = false
|
var shuffleState: Bool = false
|
||||||
|
|||||||
@ -39,28 +39,8 @@ func playerReducer(action: Action, state: PlayerState?) -> PlayerState {
|
|||||||
case let action as UpdateCurrentSongAction:
|
case let action as UpdateCurrentSongAction:
|
||||||
state.currentSong = action.currentSong
|
state.currentSong = action.currentSong
|
||||||
|
|
||||||
if let currentSong = state.currentSong {
|
|
||||||
let coverArtService = CoverArtService(path: currentSong.mpdSong.path, album: currentSong.album)
|
|
||||||
|
|
||||||
coverArtService.fetchBigCoverArt()
|
|
||||||
.done() { image in
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
if let image = image {
|
|
||||||
App.store.dispatch(UpdateCurrentCoverArtAction(coverArt: image))
|
|
||||||
} else {
|
|
||||||
App.store.dispatch(UpdateCurrentCoverArtAction(coverArt: .defaultCoverArt))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.cauterize()
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
App.store.dispatch(UpdateCurrentCoverArtAction(coverArt: .defaultCoverArt))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case let action as UpdateCurrentCoverArtAction:
|
case let action as UpdateCurrentCoverArtAction:
|
||||||
state.currentArtwork = action.coverArt
|
break
|
||||||
|
|
||||||
case let action as UpdateElapsedTimeAction:
|
case let action as UpdateElapsedTimeAction:
|
||||||
state.elapsedTimeMs = action.elapsedTimeMs
|
state.elapsedTimeMs = action.elapsedTimeMs
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user