cog/Playlist/PlaylistEntry.m

535 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