Cog/Playlist/PlaylistEntry.m
Christopher Snowhill 0f10543566 Improve spam formatting string in two ways
For one thing, improve it so that if the tracknumber field is present,
but zero, it doesn't show the number sign in the spam. For the second,
only show the track artist field if it differs from the album artist.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-02-26 01:10:27 -08:00

534 lines
13 KiB
Objective-C

//
// PlaylistEntry.m
// Cog
//
// Created by Vincent Spader on 3/14/05.
// Copyright 2005 Vincent Spader All rights reserved.
//
#import "PlaylistEntry.h"
#import "AVIFDecoder.h"
#import "SecondsFormatter.h"
@implementation PlaylistEntry
@synthesize index;
@synthesize shuffleIndex;
@synthesize dbIndex;
@synthesize entryId;
@synthesize artId;
@synthesize current;
@synthesize removed;
@synthesize stopAfter;
@synthesize queued;
@synthesize queuePosition;
@synthesize error;
@synthesize errorMessage;
@synthesize URL;
@synthesize trashURL;
@synthesize artist;
@synthesize albumartist;
@synthesize album;
@synthesize genre;
@synthesize year;
@synthesize track;
@synthesize disc;
@synthesize cuesheet;
@synthesize totalFrames;
@synthesize bitrate;
@synthesize channels;
@synthesize channelConfig;
@synthesize bitsPerSample;
@synthesize floatingPoint;
@synthesize Unsigned;
@synthesize sampleRate;
@synthesize codec;
@synthesize replayGainAlbumGain;
@synthesize replayGainAlbumPeak;
@synthesize replayGainTrackGain;
@synthesize replayGainTrackPeak;
@synthesize volume;
@synthesize currentPosition;
@synthesize endian;
@synthesize encoding;
@synthesize seekable;
@synthesize metadataLoaded;
@synthesize deleted;
// The following read-only keys depend on the values of other properties
+ (NSSet *)keyPathsForValuesAffectingDisplay {
return [NSSet setWithObjects:@"artist", @"title", nil];
}
+ (NSSet *)keyPathsForValuesAffectingLength {
return [NSSet setWithObjects:@"metadataLoaded", @"totalFrames", @"sampleRate", nil];
}
+ (NSSet *)keyPathsForValuesAffectingPath {
return [NSSet setWithObject:@"URL"];
}
+ (NSSet *)keyPathsForValuesAffectingFilename {
return [NSSet setWithObject:@"URL"];
}
+ (NSSet *)keyPathsForValuesAffectingStatus {
return [NSSet setWithObjects:@"current", @"queued", @"error", @"stopAfter", nil];
}
+ (NSSet *)keyPathsForValuesAffectingStatusMessage {
return [NSSet setWithObjects:@"current", @"queued", @"queuePosition", @"error", @"errorMessage", @"stopAfter", nil];
}
+ (NSSet *)keyPathsForValuesAffectingSpam {
return [NSSet setWithObjects:@"albumartist", @"artist", @"title", @"album", @"track", @"disc", @"totalFrames", @"currentPosition", @"bitrate", nil];
}
+ (NSSet *)keyPathsForValuesAffectingTrackText {
return [NSSet setWithObjects:@"track", @"disc", nil];
}
+ (NSSet *)keyPathsForValuesAffectingYearText {
return [NSSet setWithObject:@"year"];
}
+ (NSSet *)keyPathsForValuesAffectingCuesheetPresent {
return [NSSet setWithObject:@"cuesheet"];
}
+ (NSSet *)keyPathsForValuesAffectingPositionText {
return [NSSet setWithObject:@"currentPosition"];
}
+ (NSSet *)keyPathsForValuesAffectingLengthText {
return [NSSet setWithObject:@"length"];
}
+ (NSSet *)keyPathsForValuesAffectingAlbumArt {
return [NSSet setWithObjects:@"albumArtInternal", @"artId", nil];
}
+ (NSSet *)keyPathsForValuesAffectingGainCorrection {
return [NSSet setWithObjects:@"replayGainAlbumGain", @"replayGainAlbumPeak", @"replayGainTrackGain", @"replayGainTrackPeak", @"volume", nil];
}
+ (NSSet *)keyPathsForValuesAffectingGainInfo {
return [NSSet setWithObjects:@"replayGainAlbumGain", @"replayGainAlbumPeak", @"replayGainTrackGain", @"replayGainTrackPeak", @"volume", nil];
}
- (NSString *)description {
return [NSString stringWithFormat:@"PlaylistEntry %li:(%@)", self.index, self.URL];
}
- (id)init {
if(self = [super init]) {
self.replayGainAlbumGain = 0;
self.replayGainAlbumPeak = 0;
self.replayGainTrackGain = 0;
self.replayGainTrackPeak = 0;
self.volume = 1;
self.deleted = NO;
}
return self;
}
- (void)dealloc {
self.errorMessage = nil;
self.URL = nil;
self.artist = nil;
self.albumartist = nil;
self.album = nil;
self.title = nil;
self.genre = nil;
self.year = nil;
self.track = nil;
self.disc = nil;
self.albumArtInternal = nil;
self.cuesheet = nil;
self.endian = nil;
self.codec = nil;
}
// Get the URL if the title is blank
@synthesize title;
- (NSString *)title {
if((title == nil || [title isEqualToString:@""]) && self.URL) {
return [[self.URL path] lastPathComponent];
}
return title;
}
@synthesize rawTitle;
- (NSString *)rawTitle {
return title;
}
@dynamic display;
- (NSString *)display {
if((self.artist == NULL) || ([self.artist isEqualToString:@""]))
return self.title;
else {
return [NSString stringWithFormat:@"%@ - %@", self.artist, self.title];
}
}
@dynamic spam;
- (NSString *)spam {
BOOL hasBitrate = (self.bitrate != 0);
BOOL hasArtist = (self.artist != nil) && (![self.artist isEqualToString:@""]);
BOOL hasAlbumArtist = (self.albumartist != nil) && (![self.albumartist isEqualToString:@""]);
BOOL hasTrackArtist = (hasArtist && hasAlbumArtist) && (![self.albumartist isEqualToString:self.artist]);
BOOL hasAlbum = (self.album != nil) && (![self.album isEqualToString:@""]);
BOOL hasTrack = (self.track != 0) && ([self.track intValue] != 0);
BOOL hasLength = (self.totalFrames != 0);
BOOL hasCurrentPosition = (self.currentPosition != 0) && (self.current);
BOOL hasExtension = NO;
BOOL hasTitle = (title != nil) && (![title isEqualToString:@""]);
BOOL hasCodec = (self.codec != nil) && (![self.codec isEqualToString:@""]);
NSMutableString *filename = [NSMutableString stringWithString:[self filename]];
NSRange dotPosition = [filename rangeOfString:@"." options:NSBackwardsSearch];
NSString *extension = nil;
if(dotPosition.length > 0) {
dotPosition.location++;
dotPosition.length = [filename length] - dotPosition.location;
extension = [filename substringWithRange:dotPosition];
dotPosition.location--;
dotPosition.length++;
[filename deleteCharactersInRange:dotPosition];
hasExtension = YES;
}
NSMutableArray *elements = [NSMutableArray array];
if(hasExtension) {
[elements addObject:@"["];
if(hasCodec) {
[elements addObject:self.codec];
} else {
[elements addObject:[extension uppercaseString]];
}
if(hasBitrate) {
[elements addObject:@"@"];
[elements addObject:[NSString stringWithFormat:@"%u", self.bitrate]];
[elements addObject:@"kbps"];
}
[elements addObject:@"] "];
}
if(hasArtist) {
if(hasAlbumArtist) {
[elements addObject:self.albumartist];
} else {
[elements addObject:self.artist];
}
[elements addObject:@" - "];
}
if(hasAlbum) {
[elements addObject:@"["];
[elements addObject:self.album];
if(hasTrack) {
[elements addObject:@" #"];
[elements addObject:self.trackText];
}
[elements addObject:@"] "];
}
if(hasTitle) {
[elements addObject:title];
} else {
[elements addObject:filename];
}
if(hasTrackArtist) {
[elements addObject:@" // "];
[elements addObject:self.artist];
}
if(hasCurrentPosition || hasLength) {
SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
[elements addObject:@" ("];
if(hasCurrentPosition) {
[elements addObject:[secondsFormatter stringForObjectValue:[NSNumber numberWithFloat:currentPosition]]];
}
if(hasLength) {
if(hasCurrentPosition) {
[elements addObject:@" / "];
}
[elements addObject:[secondsFormatter stringForObjectValue:[self length]]];
}
[elements addObject:@")"];
}
return [elements componentsJoinedByString:@""];
}
@dynamic trackText;
- (NSString *)trackText {
if([self.track intValue]) {
if([self.disc intValue]) {
return [NSString stringWithFormat:@"%@.%02u", self.disc, [self.track intValue]];
} else {
return [NSString stringWithFormat:@"%02u", [self.track intValue]];
}
} else {
return @"";
}
}
@dynamic yearText;
- (NSString *)yearText {
if([self.year intValue]) {
return [NSString stringWithFormat:@"%@", self.year];
} else {
return @"";
}
}
@dynamic cuesheetPresent;
- (NSString *)cuesheetPresent {
if(cuesheet && [cuesheet length]) {
return @"yes";
} else {
return @"no";
}
}
@dynamic gainCorrection;
- (NSString *)gainCorrection {
if(replayGainAlbumGain) {
if(replayGainAlbumPeak)
return @"Album Gain plus Peak";
else
return @"Album Gain";
} else if(replayGainTrackGain) {
if(replayGainTrackPeak)
return @"Track Gain plus Peak";
else
return @"Track Gain";
} else if(volume && volume != 1) {
return @"Volume scale";
} else {
return @"None";
}
}
@dynamic gainInfo;
- (NSString *)gainInfo {
NSMutableArray *gainItems = [[NSMutableArray alloc] init];
if(replayGainAlbumGain) {
[gainItems addObject:[NSString stringWithFormat:@"Album Gain: %+.2f dB", replayGainAlbumGain]];
}
if(replayGainAlbumPeak) {
[gainItems addObject:[NSString stringWithFormat:@"Album Peak: %.6f", replayGainAlbumPeak]];
}
if(replayGainTrackGain) {
[gainItems addObject:[NSString stringWithFormat:@"Track Gain: %+.2f dB", replayGainTrackGain]];
}
if(replayGainTrackPeak) {
[gainItems addObject:[NSString stringWithFormat:@"Track Peak: %.6f", replayGainTrackPeak]];
}
if(volume && volume != 1) {
[gainItems addObject:[NSString stringWithFormat:@"Volume Scale: %.2f%C", volume, (unichar)0x00D7]];
}
return [gainItems componentsJoinedByString:@"\n"];
}
@dynamic positionText;
- (NSString *)positionText {
SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
NSString *time = [secondsFormatter stringForObjectValue:[NSNumber numberWithFloat:currentPosition]];
return time;
}
@dynamic lengthText;
- (NSString *)lengthText {
SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
NSString *time = [secondsFormatter stringForObjectValue:[self length]];
return time;
}
@synthesize albumArtInternal;
@dynamic albumArt;
- (NSImage *)albumArt {
if(!albumArtInternal || ![albumArtInternal length]) return nil;
NSString *imageCacheTag = [NSString stringWithFormat:@"%ld", artId];
NSImage *image = [NSImage imageNamed:imageCacheTag];
if(image == nil) {
if([AVIFDecoder isAVIFFormatForData:albumArtInternal]) {
CGImageRef imageRef = [AVIFDecoder createAVIFImageWithData:albumArtInternal];
if(imageRef) {
image = [[NSImage alloc] initWithCGImage:imageRef size:NSZeroSize];
CFRelease(imageRef);
}
} else {
image = [[NSImage alloc] initWithData:albumArtInternal];
}
[image setName:imageCacheTag];
}
return image;
}
- (void)setAlbumArt:(id)data {
if([data isKindOfClass:[NSData class]]) {
[self setAlbumArtInternal:data];
}
}
@dynamic length;
- (NSNumber *)length {
return [NSNumber numberWithDouble:(self.metadataLoaded) ? ((double)self.totalFrames / self.sampleRate) : 0.0];
}
@dynamic path;
- (NSString *)path {
if([self.URL isFileURL])
return [[self.URL path] stringByAbbreviatingWithTildeInPath];
else
return [self.URL absoluteString];
}
@dynamic filename;
- (NSString *)filename {
return [[self.URL path] lastPathComponent];
}
@dynamic status;
- (NSString *)status {
if(self.stopAfter) {
return @"stopAfter";
} else if(self.current) {
return @"playing";
} else if(self.queued) {
return @"queued";
} else if(self.error) {
return @"error";
}
return nil;
}
@dynamic statusMessage;
- (NSString *)statusMessage {
if(self.stopAfter) {
return @"Stopping once finished...";
} else if(self.current) {
return @"Playing...";
} else if(self.queued) {
return [NSString stringWithFormat:@"Queued: %li", self.queuePosition + 1];
} else if(self.error) {
return errorMessage;
}
return nil;
}
- (void)setMetadata:(NSDictionary *)metadata {
if(metadata == nil) {
self.error = YES;
self.errorMessage = @"Unable to retrieve metadata.";
} else {
[self setValuesForKeysWithDictionary:metadata];
}
[self setMetadataLoaded:YES];
}
// Now we duplicate the object to a new handle, but merely reference the same data
- (id)copyWithZone:(NSZone *)zone {
PlaylistEntry *pe = [[[self class] allocWithZone:zone] init];
if(pe) {
pe->index = index;
pe->shuffleIndex = shuffleIndex;
pe->dbIndex = dbIndex;
pe->entryId = entryId;
pe->artId = artId;
pe->current = current;
pe->removed = removed;
pe->stopAfter = stopAfter;
pe->queued = queued;
pe->queuePosition = queuePosition;
pe->error = error;
pe->errorMessage = errorMessage;
pe->URL = URL;
pe->artist = artist;
pe->albumartist = albumartist;
pe->album = album;
pe->title = title;
pe->genre = genre;
pe->year = year;
pe->track = track;
pe->disc = disc;
pe->cuesheet = cuesheet;
pe->albumArtInternal = albumArtInternal;
pe->replayGainAlbumGain = replayGainAlbumGain;
pe->replayGainAlbumPeak = replayGainAlbumPeak;
pe->replayGainTrackGain = replayGainTrackGain;
pe->replayGainTrackPeak = replayGainTrackPeak;
pe->volume = volume;
currentPosition = pe->currentPosition;
pe->totalFrames = totalFrames;
pe->bitrate = bitrate;
pe->channels = channels;
pe->channelConfig = channelConfig;
pe->bitsPerSample = bitsPerSample;
pe->floatingPoint = floatingPoint;
pe->Unsigned = Unsigned;
pe->sampleRate = sampleRate;
pe->codec = codec;
pe->endian = endian;
pe->encoding = encoding;
pe->seekable = seekable;
pe->metadataLoaded = metadataLoaded;
pe->deleted = deleted;
}
return pe;
}
@end