Skip to content

Commit

Permalink
Playable: extract artwork from ID3 tag
Browse files Browse the repository at this point in the history
  • Loading branch information
BLeeEZ committed Nov 7, 2021
1 parent d50fffd commit 706e2e4
Show file tree
Hide file tree
Showing 17 changed files with 390 additions and 17 deletions.
16 changes: 15 additions & 1 deletion Amperfy.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@
509121032625EF14008C57EC /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509121022625EF14008C57EC /* MarqueeLabel.swift */; };
5095F98C23C8389E008B0805 /* MusicPlayerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5095F98B23C8389E008B0805 /* MusicPlayerTest.swift */; };
50973ADD2636E4C4005497CA /* SsGenreParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50973ADC2636E4C4005497CA /* SsGenreParserDelegate.swift */; };
50A12F432739239800E7B2AA /* EmbeddedArtworkMO+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A12F412739239800E7B2AA /* EmbeddedArtworkMO+CoreDataClass.swift */; };
50A12F442739239800E7B2AA /* EmbeddedArtworkMO+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A12F422739239800E7B2AA /* EmbeddedArtworkMO+CoreDataProperties.swift */; };
50A12F4627392B2400E7B2AA /* EmbeddedArtwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A12F4527392B2400E7B2AA /* EmbeddedArtwork.swift */; };
50A738812244B03700D3F2D4 /* SsPlayableParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A738802244B03700D3F2D4 /* SsPlayableParserDelegate.swift */; };
50A7388322451ED200D3F2D4 /* SsPlaylistParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A7388222451ED200D3F2D4 /* SsPlaylistParserDelegate.swift */; };
50A7388522452C1900D3F2D4 /* SsPlaylistSongsParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A7388422452C1900D3F2D4 /* SsPlaylistSongsParserDelegate.swift */; };
Expand Down Expand Up @@ -511,6 +514,10 @@
5095F98B23C8389E008B0805 /* MusicPlayerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicPlayerTest.swift; sourceTree = "<group>"; };
50964D5723B54BBA00B685B2 /* Amperfy v3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Amperfy v3.xcdatamodel"; sourceTree = "<group>"; };
50973ADC2636E4C4005497CA /* SsGenreParserDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SsGenreParserDelegate.swift; sourceTree = "<group>"; };
50A12F402739203E00E7B2AA /* Amperfy v15.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Amperfy v15.xcdatamodel"; sourceTree = "<group>"; };
50A12F412739239800E7B2AA /* EmbeddedArtworkMO+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EmbeddedArtworkMO+CoreDataClass.swift"; sourceTree = "<group>"; };
50A12F422739239800E7B2AA /* EmbeddedArtworkMO+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EmbeddedArtworkMO+CoreDataProperties.swift"; sourceTree = "<group>"; };
50A12F4527392B2400E7B2AA /* EmbeddedArtwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedArtwork.swift; sourceTree = "<group>"; };
50A738802244B03700D3F2D4 /* SsPlayableParserDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SsPlayableParserDelegate.swift; sourceTree = "<group>"; };
50A7388222451ED200D3F2D4 /* SsPlaylistParserDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SsPlaylistParserDelegate.swift; sourceTree = "<group>"; };
50A7388422452C1900D3F2D4 /* SsPlaylistSongsParserDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SsPlaylistSongsParserDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -774,6 +781,8 @@
50F81ED723BF6B1E00EAAC3E /* PlaylistItemMO+CoreDataProperties.swift */,
50F81ED823BF6B1E00EAAC3E /* ArtworkMO+CoreDataClass.swift */,
50F81ED923BF6B1E00EAAC3E /* ArtworkMO+CoreDataProperties.swift */,
50A12F412739239800E7B2AA /* EmbeddedArtworkMO+CoreDataClass.swift */,
50A12F422739239800E7B2AA /* EmbeddedArtworkMO+CoreDataProperties.swift */,
50F81EDA23BF6B1E00EAAC3E /* SongMO+CoreDataClass.swift */,
50F81EDB23BF6B1E00EAAC3E /* SongMO+CoreDataProperties.swift */,
501A7D442681C11C0055A51B /* PodcastEpisodeMO+CoreDataClass.swift */,
Expand Down Expand Up @@ -999,6 +1008,7 @@
50DEEE1A265ED65B0073FF20 /* MusicFolder.swift */,
501858F7265F779B00F2E13A /* Directory.swift */,
501A7D3C2680A9AE0055A51B /* Podcast.swift */,
50A12F4527392B2400E7B2AA /* EmbeddedArtwork.swift */,
);
path = EntityWrappers;
sourceTree = "<group>";
Expand Down Expand Up @@ -1597,6 +1607,7 @@
501858F8265F779C00F2E13A /* Directory.swift in Sources */,
502C0045224C0B1400DA0D1E /* SubsonicArtworkDownloadDelegate.swift in Sources */,
5013E68626834F450020D8B5 /* PodcastDetailTableHeader.swift in Sources */,
50A12F442739239800E7B2AA /* EmbeddedArtworkMO+CoreDataProperties.swift in Sources */,
50BA930021CFDEBD00E5901D /* LibraryVC.swift in Sources */,
505A45F4268B53D30031B08B /* MappingModelV10toV11.xcmappingmodel in Sources */,
50BA92FA21CC37F800E5901D /* SongsVC.swift in Sources */,
Expand All @@ -1610,6 +1621,7 @@
5088A8A526A955AD00B4C8C6 /* DownloadProtocols.swift in Sources */,
50763F1A22AD942A00D062A0 /* AlbumDetailTableHeader.swift in Sources */,
5083861A21C827E600C4BB32 /* AuthParserDelegate.swift in Sources */,
50A12F432739239800E7B2AA /* EmbeddedArtworkMO+CoreDataClass.swift in Sources */,
50DEEE1B265ED65B0073FF20 /* MusicFolder.swift in Sources */,
50BA930221D1019B00E5901D /* AmpacheLibrarySyncer.swift in Sources */,
500B7D4321ECFCDD0083ED6F /* LibraryElementEnum.swift in Sources */,
Expand Down Expand Up @@ -1650,6 +1662,7 @@
50C16C3221EA77C700F086F0 /* AmpacheArtworkDownloadDelegate.swift in Sources */,
5029288E2657B78B00DA3A8F /* UserStatisticsMO+CoreDataClass.swift in Sources */,
50C16C1021E5463E00F086F0 /* ArtistsVC.swift in Sources */,
50A12F4627392B2400E7B2AA /* EmbeddedArtwork.swift in Sources */,
50065EB421F0584D0066FC32 /* PlaylistSelectorVC.swift in Sources */,
500B7D4121EC893B0083ED6F /* SongActionSheetView.swift in Sources */,
50F81EDC23BF6B1E00EAAC3E /* ArtistMO+CoreDataClass.swift in Sources */,
Expand Down Expand Up @@ -2075,6 +2088,7 @@
500BB49521CAAA2700D367CF /* Amperfy.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
50A12F402739203E00E7B2AA /* Amperfy v15.xcdatamodel */,
509001BD2717120900A8056D /* Amperfy v14.xcdatamodel */,
50FE808B26A55A8300CB434D /* Amperfy v13.xcdatamodel */,
5040B2C2269C451A00911451 /* Amperfy v12.xcdatamodel */,
Expand All @@ -2090,7 +2104,7 @@
506B3A3823B4539D00E31F21 /* Amperfy v2.xcdatamodel */,
500BB49621CAAA2700D367CF /* Amperfy.xcdatamodel */,
);
currentVersion = 509001BD2717120900A8056D /* Amperfy v14.xcdatamodel */;
currentVersion = 50A12F402739203E00E7B2AA /* Amperfy v15.xcdatamodel */;
path = Amperfy.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
Expand Down
2 changes: 1 addition & 1 deletion Amperfy/Common/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}()
lazy var player: MusicPlayer = {
let backendAudioPlayer = BackendAudioPlayer(mediaPlayer: AVPlayer(), eventLogger: eventLogger, backendApi: backendApi, playableDownloader: playableDownloadManager, cacheProxy: library, userStatistics: userStatistics)
let curPlayer = MusicPlayer(coreData: library.getPlayerData(), playableDownloadManager: playableDownloadManager, backendAudioPlayer: backendAudioPlayer, userStatistics: userStatistics)
let curPlayer = MusicPlayer(coreData: library.getPlayerData(), library: library, playableDownloadManager: playableDownloadManager, backendAudioPlayer: backendAudioPlayer, userStatistics: userStatistics)
curPlayer.isOfflineMode = persistentStorage.settings.isOfflineMode
return curPlayer
}()
Expand Down
6 changes: 6 additions & 0 deletions Amperfy/Common/Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,12 @@ extension Data {
var sizeInByte: Int64 {
return Int64(count)
}
func createLocalUrl(fileName: String? = nil) -> URL {
let tempDirectoryURL = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true)
let url = tempDirectoryURL.appendingPathComponent(fileName ?? UUID().uuidString)
try! self.write(to: url, options: Data.WritingOptions.atomic)
return url
}
}

extension Date {
Expand Down
19 changes: 12 additions & 7 deletions Amperfy/Player/BackendAudioPlayer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import AVFoundation
import UIKit
import os.log

protocol BackendAudioPlayerNotifiable {
Expand Down Expand Up @@ -125,8 +126,8 @@ class BackendAudioPlayer {
guard let playable = playlistItem.playable, let playableData = cacheProxy.getFile(forPlayable: playable)?.data else { return }
os_log(.default, "Play item: %s", playable.displayString)
if playable.isSong { userStatistics.playedSong(isPlayedFromCache: true) }
let url = createLocalUrl(forFileData: playableData)
insert(playable: playable, withUrl: url)
let itemUrl = playableData.createLocalUrl(fileName: "curPlayItem.mp3")
insert(playable: playable, withUrl: itemUrl)
}

private func insertStreamPlayable(playlistItem: PlaylistItem) {
Expand Down Expand Up @@ -155,11 +156,15 @@ class BackendAudioPlayer {
self.responder?.notifyItemPreparationFinished()
}

private func createLocalUrl(forFileData fileData: Data) -> URL {
let tempDirectoryURL = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true)
let url = tempDirectoryURL.appendingPathComponent("curPlayItem.mp3")
try! fileData.write(to: url, options: Data.WritingOptions.atomic)
return url
func getEmbeddedArtworkFromID3Tag() -> UIImage? {
guard let item = player.currentItem else { return nil }
let metadataList = item.asset.metadata
guard let artworkAsset = metadataList.filter({ $0.commonKey == .commonKeyArtwork }).first,
let artworkData = artworkAsset.dataValue,
let artworkImage = UIImage(data: artworkData) else {
return nil
}
return artworkImage
}

}
22 changes: 21 additions & 1 deletion Amperfy/Player/MusicPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ protocol MusicPlayable {
func didStopPlaying(playlistItem: PlaylistItem?)
func didElapsedTimeChange()
func didPlaylistChange()
func didArtworkChange()
}

enum RepeatMode: Int16 {
Expand Down Expand Up @@ -88,15 +89,17 @@ class MusicPlayer: NSObject, BackendAudioPlayerNotifiable {
}

private var coreData: PlayerData
private let library: LibraryStorage
private var playableDownloadManager: DownloadManageable
private let backendAudioPlayer: BackendAudioPlayer
private let userStatistics: UserStatistics
private var notifierList = [MusicPlayable]()
private let replayInsteadPlayPreviousTimeInSec = 5.0
private var remoteCommandCenter: MPRemoteCommandCenter?

init(coreData: PlayerData, playableDownloadManager: DownloadManageable, backendAudioPlayer: BackendAudioPlayer, userStatistics: UserStatistics) {
init(coreData: PlayerData, library: LibraryStorage, playableDownloadManager: DownloadManageable, backendAudioPlayer: BackendAudioPlayer, userStatistics: UserStatistics) {
self.coreData = coreData
self.library = library
self.playableDownloadManager = playableDownloadManager
self.backendAudioPlayer = backendAudioPlayer
self.backendAudioPlayer.isAutoCachePlayedItems = coreData.isAutoCachePlayedItems
Expand Down Expand Up @@ -154,10 +157,21 @@ class MusicPlayer: NSObject, BackendAudioPlayerNotifiable {
let playlistItem = playlist.items[playlistIndex]
userStatistics.playedItem(repeatMode: repeatMode, isShuffle: isShuffle)
backendAudioPlayer.requestToPlay(playlistItem: playlistItem)
extractEmbeddedArtwork(playlistItem: playlistItem)
coreData.currentIndex = playlistIndex
preDownloadNextItems(playlistIndex: playlistIndex)
}

private func extractEmbeddedArtwork(playlistItem: PlaylistItem) {
if let embeddedImage = backendAudioPlayer.getEmbeddedArtworkFromID3Tag(), playlistItem.playable?.embeddedArtwork == nil {
let embeddedArtwork = library.createEmbeddedArtwork()
embeddedArtwork.setImage(fromData: embeddedImage.pngData())
embeddedArtwork.owner = playlistItem.playable
library.saveContext()
notifyArtworkChanged()
}
}

private func preDownloadNextItems(playlistIndex: Int) {
guard coreData.isAutoCachePlayedItems else { return }
var upcomingItemsCount = (playlist.playables.count-1) - playlistIndex
Expand Down Expand Up @@ -349,6 +363,12 @@ class MusicPlayer: NSObject, BackendAudioPlayerNotifiable {
}
}

func notifyArtworkChanged() {
for notifier in notifierList {
notifier.didArtworkChange()
}
}

func notifyElapsedTimeChanged() {
for notifier in notifierList {
notifier.didElapsedTimeChange()
Expand Down
21 changes: 17 additions & 4 deletions Amperfy/Screens/View/PlayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -368,31 +368,40 @@ class PlayerView: UIView {
}

func refreshCurrentlyPlayingInfo() {
refreshArtwork()
if player.playlist.playables.count > 0 {
let playableIndex = player.currentlyPlaying?.index ?? 0
let playableInfo = player.playlist.playables[playableIndex]
titleCompactLabel.text = playableInfo.title
titleLargeLabel.text = playableInfo.title
artistNameCompactLabel.text = playableInfo.creatorName
artistNameLargeLabel.text = playableInfo.creatorName
artworkImage.image = playableInfo.image
rootView?.popupItem.title = playableInfo.title
rootView?.popupItem.subtitle = playableInfo.creatorName
rootView?.popupItem.image = playableInfo.image
rootView?.changeBackgroundGradient(forPlayable: playableInfo)
lastDisplayedPlayable = playableInfo
} else {
titleCompactLabel.text = "Not playing"
titleLargeLabel.text = "Not playing"
artistNameCompactLabel.text = ""
artistNameLargeLabel.text = ""
artworkImage.image = Artwork.defaultImage
rootView?.popupItem.title = "Not playing"
rootView?.popupItem.subtitle = ""
rootView?.popupItem.image = Artwork.defaultImage
lastDisplayedPlayable = nil
}
}

func refreshArtwork() {
if player.playlist.playables.count > 0 {
let playableIndex = player.currentlyPlaying?.index ?? 0
let playableInfo = player.playlist.playables[playableIndex]
artworkImage.image = playableInfo.image
rootView?.popupItem.image = playableInfo.image
} else {
artworkImage.image = Artwork.defaultImage
rootView?.popupItem.image = Artwork.defaultImage
}
}

func refreshTimeInfo() {
if player.currentlyPlaying != nil {
Expand Down Expand Up @@ -479,5 +488,9 @@ extension PlayerView: MusicPlayable {
func didPlaylistChange() {
refreshPlayer()
}

func didArtworkChange() {
refreshArtwork()
}

}
3 changes: 3 additions & 0 deletions Amperfy/Screens/ViewController/PopupPlayerVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,9 @@ extension PopupPlayerVC: MusicPlayable {
self.reloadData()
}

func didArtworkChange() {
}

func closePopupPlayerAndDisplayInLibraryTab(vc: UIViewController) {
guard let hostingTabBarVC = hostingTabBarVC else { return }
hostingTabBarVC.closePopup(animated: true, completion: { () in
Expand Down
18 changes: 18 additions & 0 deletions Amperfy/Storage/EntityWrappers/AbstractPlayable.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation
import CoreData
import UIKit
import AVFoundation

public class AbstractPlayable: AbstractLibraryEntity, Downloadable {
/*
Expand All @@ -16,6 +17,23 @@ public class AbstractPlayable: AbstractLibraryEntity, Downloadable {
super.init(managedObject: managedObject)
}

override var image: UIImage {
guard let embeddedArtwork = playableManagedObject.embeddedArtwork,
let embeddedArtworkImageData = embeddedArtwork.imageData,
let embeddedArtworkImage = UIImage(data: embeddedArtworkImageData) else {
return super.image
}
return embeddedArtworkImage
}
var embeddedArtwork: EmbeddedArtwork? {
get {
guard let embeddedArtworkMO = playableManagedObject.embeddedArtwork else { return nil }
return EmbeddedArtwork(managedObject: embeddedArtworkMO)
}
set {
if playableManagedObject.embeddedArtwork != newValue?.managedObject { playableManagedObject.embeddedArtwork = newValue?.managedObject }
}
}
var objectID: NSManagedObjectID {
return playableManagedObject.objectID
}
Expand Down
29 changes: 29 additions & 0 deletions Amperfy/Storage/EntityWrappers/EmbeddedArtwork.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Foundation
import UIKit

public class EmbeddedArtwork: NSObject {

let managedObject: EmbeddedArtworkMO

init(managedObject: EmbeddedArtworkMO) {
self.managedObject = managedObject
}

var image: UIImage? {
guard let imageData = managedObject.imageData else { return nil }
return UIImage(data: imageData)
}

func setImage(fromData: Data?) {
managedObject.imageData = fromData
}

var owner: AbstractPlayable? {
get {
guard let ownerMO = managedObject.owner else { return nil }
return AbstractPlayable(managedObject: ownerMO)
}
set { if managedObject.owner != newValue?.playableManagedObject { managedObject.owner = newValue?.playableManagedObject } }
}

}
14 changes: 13 additions & 1 deletion Amperfy/Storage/LibraryStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ enum PlaylistSearchCategory: Int {

class LibraryStorage: PlayableFileCachable {

static let entitiesToDelete = [Genre.typeName, Artist.typeName, Album.typeName, Song.typeName, PlayableFile.typeName, Artwork.typeName, SyncWave.typeName, Playlist.typeName, PlaylistItem.typeName, PlayerData.entityName, LogEntry.typeName, MusicFolder.typeName, Directory.typeName, Podcast.typeName, PodcastEpisode.typeName, Download.typeName]
static let entitiesToDelete = [Genre.typeName, Artist.typeName, Album.typeName, Song.typeName, PlayableFile.typeName, Artwork.typeName, EmbeddedArtwork.typeName, SyncWave.typeName, Playlist.typeName, PlaylistItem.typeName, PlayerData.entityName, LogEntry.typeName, MusicFolder.typeName, Directory.typeName, Podcast.typeName, PodcastEpisode.typeName, Download.typeName]

private let log = OSLog(subsystem: AppDelegate.name, category: "LibraryStorage")
private var context: NSManagedObjectContext
Expand Down Expand Up @@ -211,6 +211,18 @@ class LibraryStorage: PlayableFileCachable {
clearStorage(ofType: PlayableFile.typeName)
}

func createEmbeddedArtwork() -> EmbeddedArtwork {
return EmbeddedArtwork(managedObject: EmbeddedArtworkMO(context: context))
}

func deleteEmbeddedArtworks() {
let fetchRequest = EmbeddedArtworkMO.fetchRequest()
guard let artworks = try? context.fetch(fetchRequest) else { return }
for artwork in artworks {
context.delete(artwork)
}
}

func createArtwork() -> Artwork {
return Artwork(managedObject: ArtworkMO(context: context))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ extension AbstractPlayableMO {
@NSManaged public var download: DownloadMO?
@NSManaged public var file: PlayableFileMO?
@NSManaged public var playlistItems: NSOrderedSet?
@NSManaged public var embeddedArtwork: EmbeddedArtworkMO?

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Amperfy v14.xcdatamodel</string>
<string>Amperfy v15.xcdatamodel</string>
</dict>
</plist>
Loading

0 comments on commit 706e2e4

Please sign in to comment.