Implement dynamic metadata reading for streams
Supported by FFmpeg, FLAC, Ogg Vorbis, and Opus. Signed-off-by: Christopher Snowhill <kode54@gmail.com>CQTexperiment
parent
cdf27e4786
commit
0012d1b17e
|
@ -163,7 +163,10 @@
|
|||
|
||||
if(amountInBuffer < CHUNK_SIZE) {
|
||||
int framesToRead = CHUNK_SIZE - amountInBuffer;
|
||||
int framesRead = [decoder readAudio:((char *)inputBuffer) + bytesInBuffer frames:framesToRead];
|
||||
int framesRead;
|
||||
@autoreleasepool {
|
||||
framesRead = [decoder readAudio:((char *)inputBuffer) + bytesInBuffer frames:framesToRead];
|
||||
}
|
||||
|
||||
if(framesRead > 0 && !seekError) {
|
||||
amountInBuffer += framesRead;
|
||||
|
|
|
@ -66,8 +66,37 @@
|
|||
17B5E1DE0CC074D3004E2AF4 /* window.c in Sources */ = {isa = PBXBuildFile; fileRef = 17B5E1970CC074D3004E2AF4 /* window.c */; };
|
||||
17B5E2C00CC07904004E2AF4 /* stream_encoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 17B5E1950CC074D3004E2AF4 /* stream_encoder.c */; };
|
||||
17B5E2C10CC07905004E2AF4 /* stream_encoder_framing.c in Sources */ = {isa = PBXBuildFile; fileRef = 17B5E1960CC074D3004E2AF4 /* stream_encoder_framing.c */; };
|
||||
8356BD0527B3A4C20074E50C /* ogg_mapping.c in Sources */ = {isa = PBXBuildFile; fileRef = 8356BD0127B3A4C20074E50C /* ogg_mapping.c */; };
|
||||
8356BD0627B3A4C20074E50C /* ogg_encoder_aspect.c in Sources */ = {isa = PBXBuildFile; fileRef = 8356BD0227B3A4C20074E50C /* ogg_encoder_aspect.c */; };
|
||||
8356BD0727B3A4C20074E50C /* ogg_decoder_aspect.c in Sources */ = {isa = PBXBuildFile; fileRef = 8356BD0327B3A4C20074E50C /* ogg_decoder_aspect.c */; };
|
||||
8356BD0827B3A4C20074E50C /* ogg_helper.c in Sources */ = {isa = PBXBuildFile; fileRef = 8356BD0427B3A4C20074E50C /* ogg_helper.c */; };
|
||||
8356BD1327B3A4E20074E50C /* Ogg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8356BD0F27B3A4D90074E50C /* Ogg.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
8356BD0E27B3A4D90074E50C /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 8356BD0927B3A4D90074E50C /* Ogg.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 8D07F2C80486CC7A007CD1D0;
|
||||
remoteInfo = Ogg;
|
||||
};
|
||||
8356BD1027B3A4D90074E50C /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 8356BD0927B3A4D90074E50C /* Ogg.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 734FB2E50B18B33E00D561D7;
|
||||
remoteInfo = "libogg (static)";
|
||||
};
|
||||
8356BD1427B3A4E70074E50C /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 8356BD0927B3A4D90074E50C /* Ogg.xcodeproj */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 8D07F2BC0486CC7A007CD1D0;
|
||||
remoteInfo = Ogg;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0867D69BFE84028FC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
|
||||
0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
|
||||
|
@ -131,6 +160,11 @@
|
|||
17B5E1950CC074D3004E2AF4 /* stream_encoder.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = stream_encoder.c; sourceTree = "<group>"; };
|
||||
17B5E1960CC074D3004E2AF4 /* stream_encoder_framing.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = stream_encoder_framing.c; sourceTree = "<group>"; };
|
||||
17B5E1970CC074D3004E2AF4 /* window.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = window.c; sourceTree = "<group>"; };
|
||||
8356BD0127B3A4C20074E50C /* ogg_mapping.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ogg_mapping.c; sourceTree = "<group>"; };
|
||||
8356BD0227B3A4C20074E50C /* ogg_encoder_aspect.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ogg_encoder_aspect.c; sourceTree = "<group>"; };
|
||||
8356BD0327B3A4C20074E50C /* ogg_decoder_aspect.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ogg_decoder_aspect.c; sourceTree = "<group>"; };
|
||||
8356BD0427B3A4C20074E50C /* ogg_helper.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ogg_helper.c; sourceTree = "<group>"; };
|
||||
8356BD0927B3A4D90074E50C /* Ogg.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Ogg.xcodeproj; path = ../Ogg/macosx/Ogg.xcodeproj; sourceTree = "<group>"; };
|
||||
8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
8DC2EF5B0486A6940098B216 /* FLAC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FLAC.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D2F7E79907B2D74100F64583 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
|
||||
|
@ -141,6 +175,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8356BD1327B3A4E20074E50C /* Ogg.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -163,6 +198,7 @@
|
|||
089C1665FE841158C02AAC07 /* Resources */,
|
||||
0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */,
|
||||
034768DFFF38A50411DB9C8B /* Products */,
|
||||
8356BD1227B3A4E20074E50C /* Frameworks */,
|
||||
);
|
||||
name = flac;
|
||||
sourceTree = "<group>";
|
||||
|
@ -195,6 +231,7 @@
|
|||
1058C7B2FEA5585E11CA2CBB /* Other Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8356BD0927B3A4D90074E50C /* Ogg.xcodeproj */,
|
||||
0867D6A5FE840307C02AAC07 /* AppKit.framework */,
|
||||
D2F7E79907B2D74100F64583 /* CoreData.framework */,
|
||||
0867D69BFE84028FC02AAC07 /* Foundation.framework */,
|
||||
|
@ -280,9 +317,13 @@
|
|||
17B5E1880CC074D3004E2AF4 /* memory.c */,
|
||||
17B5E1890CC074D3004E2AF4 /* metadata_iterators.c */,
|
||||
17B5E18A0CC074D3004E2AF4 /* metadata_object.c */,
|
||||
8356BD0327B3A4C20074E50C /* ogg_decoder_aspect.c */,
|
||||
8356BD0227B3A4C20074E50C /* ogg_encoder_aspect.c */,
|
||||
8356BD0427B3A4C20074E50C /* ogg_helper.c */,
|
||||
8356BD0127B3A4C20074E50C /* ogg_mapping.c */,
|
||||
17B5E1940CC074D3004E2AF4 /* stream_decoder.c */,
|
||||
17B5E1950CC074D3004E2AF4 /* stream_encoder.c */,
|
||||
17B5E1960CC074D3004E2AF4 /* stream_encoder_framing.c */,
|
||||
17B5E1950CC074D3004E2AF4 /* stream_encoder.c */,
|
||||
17B5E1970CC074D3004E2AF4 /* window.c */,
|
||||
);
|
||||
path = libFLAC;
|
||||
|
@ -333,6 +374,22 @@
|
|||
path = protected;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8356BD0A27B3A4D90074E50C /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8356BD0F27B3A4D90074E50C /* Ogg.framework */,
|
||||
8356BD1127B3A4D90074E50C /* libogg.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8356BD1227B3A4E20074E50C /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
|
@ -400,6 +457,7 @@
|
|||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
8356BD1527B3A4E70074E50C /* PBXTargetDependency */,
|
||||
);
|
||||
name = "FLAC Framework";
|
||||
productInstallPath = "$(HOME)/Library/Frameworks";
|
||||
|
@ -432,6 +490,12 @@
|
|||
mainGroup = 0867D691FE84028FC02AAC07 /* flac */;
|
||||
productRefGroup = 034768DFFF38A50411DB9C8B /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = 8356BD0A27B3A4D90074E50C /* Products */;
|
||||
ProjectRef = 8356BD0927B3A4D90074E50C /* Ogg.xcodeproj */;
|
||||
},
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
8DC2EF4F0486A6940098B216 /* FLAC Framework */,
|
||||
|
@ -439,6 +503,23 @@
|
|||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXReferenceProxy section */
|
||||
8356BD0F27B3A4D90074E50C /* Ogg.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = Ogg.framework;
|
||||
remoteRef = 8356BD0E27B3A4D90074E50C /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
8356BD1127B3A4D90074E50C /* libogg.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libogg.a;
|
||||
remoteRef = 8356BD1027B3A4D90074E50C /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
8DC2EF520486A6940098B216 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
|
@ -455,6 +536,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
17B5E1AC0CC074D3004E2AF4 /* bitmath.c in Sources */,
|
||||
8356BD0827B3A4C20074E50C /* ogg_helper.c in Sources */,
|
||||
17B5E1AD0CC074D3004E2AF4 /* bitreader.c in Sources */,
|
||||
17B5E1AE0CC074D3004E2AF4 /* bitwriter.c in Sources */,
|
||||
17B5E1AF0CC074D3004E2AF4 /* cpu.c in Sources */,
|
||||
|
@ -464,9 +546,12 @@
|
|||
17B5E1B30CC074D3004E2AF4 /* format.c in Sources */,
|
||||
17B5E1D00CC074D3004E2AF4 /* lpc.c in Sources */,
|
||||
17B5E1D10CC074D3004E2AF4 /* md5.c in Sources */,
|
||||
8356BD0727B3A4C20074E50C /* ogg_decoder_aspect.c in Sources */,
|
||||
17B5E1D20CC074D3004E2AF4 /* memory.c in Sources */,
|
||||
17B5E1D30CC074D3004E2AF4 /* metadata_iterators.c in Sources */,
|
||||
8356BD0527B3A4C20074E50C /* ogg_mapping.c in Sources */,
|
||||
17B5E1D40CC074D3004E2AF4 /* metadata_object.c in Sources */,
|
||||
8356BD0627B3A4C20074E50C /* ogg_encoder_aspect.c in Sources */,
|
||||
17B5E1DB0CC074D3004E2AF4 /* stream_decoder.c in Sources */,
|
||||
17B5E1DE0CC074D3004E2AF4 /* window.c in Sources */,
|
||||
17B5E2C00CC07904004E2AF4 /* stream_encoder.c in Sources */,
|
||||
|
@ -476,6 +561,14 @@
|
|||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
8356BD1527B3A4E70074E50C /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = Ogg;
|
||||
targetProxy = 8356BD1427B3A4E70074E50C /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
1DEB91AE08733DA50010E9CD /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
|
@ -488,6 +581,7 @@
|
|||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_VERSION = A;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_SYMBOL_SEPARATION = YES;
|
||||
|
@ -508,7 +602,7 @@
|
|||
"$(OTHER_CFLAGS_QUOTED_1)",
|
||||
"-D__MACOSX__",
|
||||
"-DHAVE_LROUND",
|
||||
"-DFLAC__HAS_OGG=0",
|
||||
"-DFLAC__HAS_OGG=1",
|
||||
);
|
||||
OTHER_CFLAGS_QUOTED_1 = "-DVERSION=\\\"1.3.3\\\" -DPACKAGE_VERSION=\\\"1.3.3\\\"";
|
||||
PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIR = NO;
|
||||
|
@ -535,6 +629,7 @@
|
|||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_VERSION = A;
|
||||
GCC_ENABLE_SYMBOL_SEPARATION = YES;
|
||||
GCC_MODEL_TUNING = G5;
|
||||
|
@ -553,7 +648,7 @@
|
|||
"$(OTHER_CFLAGS_QUOTED_1)",
|
||||
"-D__MACOSX__",
|
||||
"-DHAVE_LROUND",
|
||||
"-DFLAC__HAS_OGG=0",
|
||||
"-DFLAC__HAS_OGG=1",
|
||||
);
|
||||
OTHER_CFLAGS_QUOTED_1 = "-DVERSION=\\\"1.3.3\\\" -DPACKAGE_VERSION=\\\"1.3.3\\\"";
|
||||
PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIR = NO;
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
8356BCE727B37C6F0074E50C /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../Utils/NSDictionary+Merge.m"; sourceTree = "<group>"; };
|
||||
8356BCE827B37C6F0074E50C /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../Utils/NSDictionary+Merge.h"; sourceTree = "<group>"; };
|
||||
8356BCEA27B37DA40074E50C /* TagLibID3v2Reader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TagLibID3v2Reader.h; path = ../TagLib/TagLibID3v2Reader.h; sourceTree = "<group>"; };
|
||||
8356BD1A27B3D06F0074E50C /* HTTPSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPSource.h; path = ../HTTPSource/HTTPSource.h; sourceTree = "<group>"; };
|
||||
8384913818081F6C00E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; };
|
||||
83AA7D08279EBCC600087AA4 /* libavcodec.59.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libavcodec.59.dylib; path = ../../ThirdParty/ffmpeg/lib/libavcodec.59.dylib; sourceTree = "<group>"; };
|
||||
83AA7D09279EBCC600087AA4 /* libavutil.57.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libavutil.57.dylib; path = ../../ThirdParty/ffmpeg/lib/libavutil.57.dylib; sourceTree = "<group>"; };
|
||||
|
@ -139,6 +140,7 @@
|
|||
08FB77AFFE84173DC02AAC07 /* Classes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8356BD1A27B3D06F0074E50C /* HTTPSource.h */,
|
||||
8356BCEA27B37DA40074E50C /* TagLibID3v2Reader.h */,
|
||||
8356BCE827B37C6F0074E50C /* NSDictionary+Merge.h */,
|
||||
8356BCE727B37C6F0074E50C /* NSDictionary+Merge.m */,
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
BOOL endOfAudio;
|
||||
|
||||
int metadataIndex;
|
||||
NSString *genre;
|
||||
NSString *artist;
|
||||
NSString *title;
|
||||
NSString *album;
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
#import "Logging.h"
|
||||
|
||||
#import "HTTPSource.h"
|
||||
|
||||
#define ST_BUFF 2048
|
||||
|
||||
int ffmpeg_read(void *opaque, uint8_t *buf, int buf_size) {
|
||||
|
@ -82,8 +84,9 @@ int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) {
|
|||
// register all available codecs
|
||||
|
||||
NSURL *url = [s url];
|
||||
if([[url scheme] isEqualToString:@"http"] ||
|
||||
[[url scheme] isEqualToString:@"https"]) {
|
||||
if(([[url scheme] isEqualToString:@"http"] ||
|
||||
[[url scheme] isEqualToString:@"https"]) &&
|
||||
[[url pathExtension] isEqualToString:@"m3u8"]) {
|
||||
source = nil;
|
||||
[s close];
|
||||
|
||||
|
@ -95,20 +98,13 @@ int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) {
|
|||
return NO;
|
||||
}
|
||||
|
||||
AVDictionary *dict = NULL;
|
||||
|
||||
av_dict_set_int(&dict, "icy", 1, 0); // Enable Icy interval metadata, if supported
|
||||
|
||||
NSString *urlString = [url absoluteString];
|
||||
if((errcode = avformat_open_input(&formatCtx, [urlString UTF8String], NULL, &dict)) < 0) {
|
||||
av_dict_free(&dict);
|
||||
if((errcode = avformat_open_input(&formatCtx, [urlString UTF8String], NULL, NULL)) < 0) {
|
||||
char errDescr[4096];
|
||||
av_strerror(errcode, errDescr, 4096);
|
||||
ALog(@"Error opening file, errcode = %d, error = %s", errcode, errDescr);
|
||||
return NO;
|
||||
}
|
||||
|
||||
av_dict_free(&dict);
|
||||
} else {
|
||||
buffer = av_malloc(32 * 1024);
|
||||
if(!buffer) {
|
||||
|
@ -415,6 +411,7 @@ int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) {
|
|||
|
||||
seekable = [s seekable];
|
||||
|
||||
genre = @"";
|
||||
album = @"";
|
||||
artist = @"";
|
||||
title = @"";
|
||||
|
@ -465,6 +462,7 @@ int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) {
|
|||
|
||||
- (void)updateMetadata {
|
||||
const AVDictionaryEntry *tag = NULL;
|
||||
NSString *_genre = genre;
|
||||
NSString *_album = album;
|
||||
NSString *_artist = artist;
|
||||
NSString *_title = title;
|
||||
|
@ -481,6 +479,8 @@ int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) {
|
|||
}
|
||||
} else if(!strcasecmp(tag->key, "icy-url")) {
|
||||
_album = [NSString stringWithUTF8String:tag->value];
|
||||
} else if(!strcasecmp(tag->key, "icy-genre")) {
|
||||
_genre = [NSString stringWithUTF8String:tag->value];
|
||||
} else if(!strcasecmp(tag->key, "artist")) {
|
||||
_artist = [NSString stringWithUTF8String:tag->value];
|
||||
} else if(!strcasecmp(tag->key, "title")) {
|
||||
|
@ -489,9 +489,23 @@ int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) {
|
|||
}
|
||||
}
|
||||
|
||||
if(![_album isEqual:album] ||
|
||||
Class sourceClass = [source class];
|
||||
if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) {
|
||||
HTTPSource *httpSource = (HTTPSource *)source;
|
||||
if([httpSource hasMetadata]) {
|
||||
NSDictionary *metadata = [httpSource metadata];
|
||||
_genre = [metadata valueForKey:@"genre"];
|
||||
_album = [metadata valueForKey:@"album"];
|
||||
_artist = [metadata valueForKey:@"artist"];
|
||||
_title = [metadata valueForKey:@"title"];
|
||||
}
|
||||
}
|
||||
|
||||
if(![_genre isEqual:genre] ||
|
||||
![_album isEqual:album] ||
|
||||
![_artist isEqual:artist] ||
|
||||
![_title isEqual:title]) {
|
||||
genre = _genre;
|
||||
album = _album;
|
||||
artist = _artist;
|
||||
title = _title;
|
||||
|
@ -725,7 +739,7 @@ int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) {
|
|||
}
|
||||
|
||||
- (NSDictionary *)metadata {
|
||||
return [NSDictionary dictionaryByMerging:@{ @"album": album, @"artist": artist, @"title": title } with:id3Metadata];
|
||||
return [NSDictionary dictionaryByMerging:@{ @"genre": genre, @"album": album, @"artist": artist, @"title": title } with:id3Metadata];
|
||||
}
|
||||
|
||||
+ (NSArray *)fileTypes {
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
17C93F040B8FF67A008627D6 /* FlacDecoder.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = FlacDecoder.m; sourceTree = "<group>"; };
|
||||
17F5641A0C3BDC460019975C /* flac.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = flac.xcodeproj; path = ../../Frameworks/FLAC/flac.xcodeproj; sourceTree = SOURCE_ROOT; };
|
||||
32DBCF630370AF2F00C91783 /* Flac_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Flac_Prefix.pch; sourceTree = "<group>"; };
|
||||
8356BD1927B3CCBB0074E50C /* HTTPSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPSource.h; path = ../HTTPSource/HTTPSource.h; sourceTree = "<group>"; };
|
||||
8384912D180816C900E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; };
|
||||
8D5B49B6048680CD000E48DA /* Flac.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Flac.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8D5B49B7048680CD000E48DA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -103,6 +104,7 @@
|
|||
08FB77AFFE84173DC02AAC07 /* Classes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8356BD1927B3CCBB0074E50C /* HTTPSource.h */,
|
||||
8384912D180816C900E7332D /* Logging.h */,
|
||||
177FCFC10B90C9960011C3B5 /* Plugin.h */,
|
||||
17C93F030B8FF67A008627D6 /* FlacDecoder.h */,
|
||||
|
@ -274,6 +276,7 @@
|
|||
GCC_PREFIX_HEADER = Flac_Prefix.pch;
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INSTALL_PATH = "$(HOME)/Library/Bundles";
|
||||
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cogx.flac;
|
||||
PRODUCT_NAME = Flac;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
@ -301,6 +304,7 @@
|
|||
GCC_PREFIX_HEADER = Flac_Prefix.pch;
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INSTALL_PATH = "$(HOME)/Library/Bundles";
|
||||
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cogx.flac;
|
||||
PRODUCT_NAME = Flac;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
|
@ -34,6 +34,12 @@
|
|||
|
||||
BOOL hasStreamInfo;
|
||||
BOOL streamOpened;
|
||||
BOOL abortFlag;
|
||||
|
||||
NSString *genre;
|
||||
NSString *album;
|
||||
NSString *artist;
|
||||
NSString *title;
|
||||
}
|
||||
|
||||
- (void)setSource:(id<CogSource>)s;
|
||||
|
|
|
@ -10,30 +10,10 @@
|
|||
|
||||
#import "Logging.h"
|
||||
|
||||
#import "HTTPSource.h"
|
||||
|
||||
@implementation FlacDecoder
|
||||
|
||||
static const char *CHANNEL_MASK_TAG = "WAVEFORMATEXTENSIBLE_CHANNEL_MASK";
|
||||
|
||||
static FLAC__bool flac__utils_get_channel_mask_tag(const FLAC__StreamMetadata *object, FLAC__uint32 *channel_mask) {
|
||||
int offset;
|
||||
uint32_t val;
|
||||
char *p;
|
||||
FLAC__ASSERT(object);
|
||||
FLAC__ASSERT(object->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
|
||||
if(0 > (offset = FLAC__metadata_object_vorbiscomment_find_entry_from(object, /*offset=*/0, CHANNEL_MASK_TAG)))
|
||||
return false;
|
||||
if(object->data.vorbis_comment.comments[offset].length < strlen(CHANNEL_MASK_TAG) + 4)
|
||||
return false;
|
||||
if(0 == (p = strchr((const char *)object->data.vorbis_comment.comments[offset].entry, '='))) /* should never happen, but just in case */
|
||||
return false;
|
||||
if(strncasecmp(p, "=0x", 3))
|
||||
return false;
|
||||
if(sscanf(p + 3, "%x", &val) != 1)
|
||||
return false;
|
||||
*channel_mask = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderReadStatus ReadCallback(const FLAC__StreamDecoder *decoder, FLAC__byte blockBuffer[], size_t *bytes, void *client_data) {
|
||||
FlacDecoder *flacDecoder = (__bridge FlacDecoder *)client_data;
|
||||
long bytesRead = [[flacDecoder source] read:blockBuffer amount:*bytes];
|
||||
|
@ -99,6 +79,9 @@ FLAC__StreamDecoderLengthStatus LengthCallback(const FLAC__StreamDecoder *decode
|
|||
FLAC__StreamDecoderWriteStatus WriteCallback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const sampleblockBuffer[], void *client_data) {
|
||||
FlacDecoder *flacDecoder = (__bridge FlacDecoder *)client_data;
|
||||
|
||||
if(flacDecoder->abortFlag)
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
|
||||
uint32_t channels = frame->header.channels;
|
||||
uint32_t bitsPerSample = frame->header.bits_per_sample;
|
||||
uint32_t frequency = frame->header.sample_rate;
|
||||
|
@ -200,7 +183,7 @@ void MetadataCallback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMeta
|
|||
// to determine stream format (this seems to be consistent with flac spec: http://flac.sourceforge.net/format.html)
|
||||
FlacDecoder *flacDecoder = (__bridge FlacDecoder *)client_data;
|
||||
|
||||
if(!flacDecoder->hasStreamInfo) {
|
||||
if(!flacDecoder->hasStreamInfo && metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
|
||||
flacDecoder->channels = metadata->data.stream_info.channels;
|
||||
flacDecoder->channelConfig = 0;
|
||||
flacDecoder->frequency = metadata->data.stream_info.sample_rate;
|
||||
|
@ -212,12 +195,59 @@ void MetadataCallback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMeta
|
|||
}
|
||||
|
||||
if(metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
|
||||
(void)flac__utils_get_channel_mask_tag(metadata, &flacDecoder->channelConfig);
|
||||
NSString *_genre = flacDecoder->genre;
|
||||
NSString *_album = flacDecoder->album;
|
||||
NSString *_artist = flacDecoder->artist;
|
||||
NSString *_title = flacDecoder->title;
|
||||
uint8_t nullByte = '\0';
|
||||
const FLAC__StreamMetadata_VorbisComment *vorbis_comment = &metadata->data.vorbis_comment;
|
||||
for(int i = 0; i < vorbis_comment->num_comments; ++i) {
|
||||
NSMutableData *commentField = [NSMutableData dataWithBytes:vorbis_comment->comments[i].entry length:vorbis_comment->comments[i].length];
|
||||
[commentField appendBytes:&nullByte length:1];
|
||||
NSString *commentString = [NSString stringWithUTF8String:[commentField bytes]];
|
||||
NSArray *splitFields = [commentString componentsSeparatedByString:@"="];
|
||||
if([splitFields count] == 2) {
|
||||
NSString *name = [splitFields objectAtIndex:0];
|
||||
NSString *value = [splitFields objectAtIndex:1];
|
||||
name = [name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
name = [name lowercaseString];
|
||||
if([name isEqualToString:@"genre"]) {
|
||||
_genre = value;
|
||||
} else if([name isEqualToString:@"album"]) {
|
||||
_album = value;
|
||||
} else if([name isEqualToString:@"artist"]) {
|
||||
_artist = value;
|
||||
} else if([name isEqualToString:@"title"]) {
|
||||
_title = value;
|
||||
} else if([name isEqualToString:@"waveformatextensible_channel_mask"]) {
|
||||
if([value hasPrefix:@"0x"]) {
|
||||
char *end;
|
||||
const char *_value = [value UTF8String] + 2;
|
||||
flacDecoder->channelConfig = (uint32_t)strtoul(_value, &end, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(![_genre isEqual:flacDecoder->genre] ||
|
||||
![_album isEqual:flacDecoder->album] ||
|
||||
![_artist isEqual:flacDecoder->artist] ||
|
||||
![_title isEqual:flacDecoder->title]) {
|
||||
flacDecoder->genre = _genre;
|
||||
flacDecoder->album = _album;
|
||||
flacDecoder->artist = _artist;
|
||||
flacDecoder->title = _title;
|
||||
[flacDecoder willChangeValueForKey:@"metadata"];
|
||||
[flacDecoder didChangeValueForKey:@"metadata"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) {
|
||||
// Do nothing?
|
||||
FlacDecoder *flacDecoder = (__bridge FlacDecoder *)client_data;
|
||||
if(status != FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC)
|
||||
flacDecoder->abortFlag = YES;
|
||||
}
|
||||
|
||||
- (BOOL)open:(id<CogSource>)s {
|
||||
|
@ -230,20 +260,61 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS
|
|||
[s seek:0 whence:SEEK_SET];
|
||||
}
|
||||
|
||||
// Must peek at stream! HTTP reader supports seeking within its buffer
|
||||
BOOL isOggFlac = NO;
|
||||
uint8_t buffer[4];
|
||||
[s read:buffer amount:4];
|
||||
[s seek:0 whence:SEEK_SET];
|
||||
if(memcmp(buffer, "OggS", 4) == 0) {
|
||||
isOggFlac = YES;
|
||||
}
|
||||
|
||||
genre = @"";
|
||||
album = @"";
|
||||
artist = @"";
|
||||
title = @"";
|
||||
|
||||
decoder = FLAC__stream_decoder_new();
|
||||
if(decoder == NULL)
|
||||
return NO;
|
||||
|
||||
if(FLAC__stream_decoder_init_stream(decoder,
|
||||
ReadCallback,
|
||||
([source seekable] ? SeekCallback : NULL),
|
||||
([source seekable] ? TellCallback : NULL),
|
||||
([source seekable] ? LengthCallback : NULL),
|
||||
([source seekable] ? EOFCallback : NULL),
|
||||
WriteCallback,
|
||||
MetadataCallback,
|
||||
ErrorCallback,
|
||||
(__bridge void *)(self)) != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
||||
if(![source seekable]) {
|
||||
FLAC__stream_decoder_set_md5_checking(decoder, false);
|
||||
}
|
||||
|
||||
FLAC__stream_decoder_set_metadata_ignore_all(decoder);
|
||||
FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_STREAMINFO);
|
||||
FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT);
|
||||
|
||||
abortFlag = NO;
|
||||
|
||||
FLAC__StreamDecoderInitStatus ret;
|
||||
|
||||
if(isOggFlac) {
|
||||
ret = FLAC__stream_decoder_init_ogg_stream(decoder,
|
||||
ReadCallback,
|
||||
([source seekable] ? SeekCallback : NULL),
|
||||
([source seekable] ? TellCallback : NULL),
|
||||
([source seekable] ? LengthCallback : NULL),
|
||||
([source seekable] ? EOFCallback : NULL),
|
||||
WriteCallback,
|
||||
MetadataCallback,
|
||||
ErrorCallback,
|
||||
(__bridge void *)(self));
|
||||
} else {
|
||||
ret = FLAC__stream_decoder_init_stream(decoder,
|
||||
ReadCallback,
|
||||
([source seekable] ? SeekCallback : NULL),
|
||||
([source seekable] ? TellCallback : NULL),
|
||||
([source seekable] ? LengthCallback : NULL),
|
||||
([source seekable] ? EOFCallback : NULL),
|
||||
WriteCallback,
|
||||
MetadataCallback,
|
||||
ErrorCallback,
|
||||
(__bridge void *)(self));
|
||||
}
|
||||
|
||||
if(ret != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
@ -271,7 +342,9 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS
|
|||
break;
|
||||
}
|
||||
|
||||
FLAC__stream_decoder_process_single(decoder);
|
||||
if(!FLAC__stream_decoder_process_single(decoder)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int bytesPerFrame = ((bitsPerSample + 7) / 8) * channels;
|
||||
|
@ -292,6 +365,29 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS
|
|||
}
|
||||
}
|
||||
|
||||
Class sourceClass = [source class];
|
||||
if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) {
|
||||
HTTPSource *httpSource = (HTTPSource *)source;
|
||||
if([httpSource hasMetadata]) {
|
||||
NSDictionary *metadata = [httpSource metadata];
|
||||
NSString *_genre = [metadata valueForKey:@"genre"];
|
||||
NSString *_album = [metadata valueForKey:@"album"];
|
||||
NSString *_artist = [metadata valueForKey:@"artist"];
|
||||
NSString *_title = [metadata valueForKey:@"title"];
|
||||
if(![_genre isEqualToString:genre] ||
|
||||
![_album isEqualToString:album] ||
|
||||
![_artist isEqualToString:artist] ||
|
||||
![_title isEqualToString:title]) {
|
||||
genre = _genre;
|
||||
album = _album;
|
||||
artist = _artist;
|
||||
title = _title;
|
||||
[self willChangeValueForKey:@"metadata"];
|
||||
[self didChangeValueForKey:@"metadata"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return framesRead;
|
||||
}
|
||||
|
||||
|
@ -367,7 +463,7 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS
|
|||
}
|
||||
|
||||
- (NSDictionary *)metadata {
|
||||
return @{};
|
||||
return @{ @"genre": genre, @"album": album, @"artist": artist, @"title": title };
|
||||
}
|
||||
|
||||
+ (NSArray *)fileTypes {
|
||||
|
@ -375,7 +471,7 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS
|
|||
}
|
||||
|
||||
+ (NSArray *)mimeTypes {
|
||||
return @[@"audio/x-flac"];
|
||||
return @[@"audio/x-flac", @"application/ogg", @"audio/ogg"];
|
||||
}
|
||||
|
||||
+ (float)priority {
|
||||
|
|
|
@ -29,6 +29,11 @@
|
|||
int bitrate;
|
||||
int channels;
|
||||
long totalFrames;
|
||||
|
||||
NSString *genre;
|
||||
NSString *album;
|
||||
NSString *artist;
|
||||
NSString *title;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
#import "Logging.h"
|
||||
|
||||
#import "HTTPSource.h"
|
||||
|
||||
@implementation OpusFile
|
||||
|
||||
static const int MAXCHANNELS = 8;
|
||||
|
@ -115,9 +117,92 @@ opus_int64 sourceTell(void *_stream) {
|
|||
[self willChangeValueForKey:@"properties"];
|
||||
[self didChangeValueForKey:@"properties"];
|
||||
|
||||
genre = @"";
|
||||
album = @"";
|
||||
artist = @"";
|
||||
title = @"";
|
||||
[self updateMetadata];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)updateMetadata {
|
||||
const OpusTags *comment = op_tags(opusRef, -1);
|
||||
|
||||
if(comment) {
|
||||
uint8_t nullByte = '\0';
|
||||
NSString *_genre = genre;
|
||||
NSString *_album = album;
|
||||
NSString *_artist = artist;
|
||||
NSString *_title = title;
|
||||
for(int i = 0; i < comment->comments; ++i) {
|
||||
NSMutableData *commentField = [NSMutableData dataWithBytes:comment->user_comments[i] length:comment->comment_lengths[i]];
|
||||
[commentField appendBytes:&nullByte length:1];
|
||||
NSString *commentString = [NSString stringWithUTF8String:[commentField bytes]];
|
||||
NSArray *splitFields = [commentString componentsSeparatedByString:@"="];
|
||||
if([splitFields count] == 2) {
|
||||
NSString *name = [splitFields objectAtIndex:0];
|
||||
NSString *value = [splitFields objectAtIndex:1];
|
||||
name = [name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
name = [name lowercaseString];
|
||||
if([name isEqualToString:@"genre"]) {
|
||||
_genre = value;
|
||||
} else if([name isEqualToString:@"album"]) {
|
||||
_album = value;
|
||||
} else if([name isEqualToString:@"artist"]) {
|
||||
_artist = value;
|
||||
} else if([name isEqualToString:@"title"]) {
|
||||
_title = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(![_genre isEqual:genre] ||
|
||||
![_album isEqual:album] ||
|
||||
![_artist isEqual:artist] ||
|
||||
![_title isEqual:title]) {
|
||||
genre = _genre;
|
||||
album = _album;
|
||||
artist = _artist;
|
||||
title = _title;
|
||||
[self willChangeValueForKey:@"metadata"];
|
||||
[self didChangeValueForKey:@"metadata"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateIcyMetadata {
|
||||
NSString *_genre = genre;
|
||||
NSString *_album = album;
|
||||
NSString *_artist = artist;
|
||||
NSString *_title = title;
|
||||
|
||||
Class sourceClass = [source class];
|
||||
if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) {
|
||||
HTTPSource *httpSource = (HTTPSource *)source;
|
||||
if([httpSource hasMetadata]) {
|
||||
NSDictionary *metadata = [httpSource metadata];
|
||||
_genre = [metadata valueForKey:@"genre"];
|
||||
_album = [metadata valueForKey:@"album"];
|
||||
_artist = [metadata valueForKey:@"artist"];
|
||||
_title = [metadata valueForKey:@"title"];
|
||||
}
|
||||
}
|
||||
|
||||
if(![_genre isEqual:genre] ||
|
||||
![_album isEqual:album] ||
|
||||
![_artist isEqual:artist] ||
|
||||
![_title isEqual:title]) {
|
||||
genre = _genre;
|
||||
album = _album;
|
||||
artist = _artist;
|
||||
title = _title;
|
||||
[self willChangeValueForKey:@"metadata"];
|
||||
[self didChangeValueForKey:@"metadata"];
|
||||
}
|
||||
}
|
||||
|
||||
- (int)readAudio:(void *)buf frames:(UInt32)frames {
|
||||
int numread;
|
||||
int total = 0;
|
||||
|
@ -128,6 +213,8 @@ opus_int64 sourceTell(void *_stream) {
|
|||
|
||||
[self willChangeValueForKey:@"properties"];
|
||||
[self didChangeValueForKey:@"properties"];
|
||||
|
||||
[self updateMetadata];
|
||||
}
|
||||
|
||||
int size = frames * channels;
|
||||
|
@ -157,6 +244,8 @@ opus_int64 sourceTell(void *_stream) {
|
|||
|
||||
} while(total != size && numread != 0);
|
||||
|
||||
[self updateIcyMetadata];
|
||||
|
||||
return total / channels;
|
||||
}
|
||||
|
||||
|
@ -189,7 +278,7 @@ opus_int64 sourceTell(void *_stream) {
|
|||
}
|
||||
|
||||
- (NSDictionary *)metadata {
|
||||
return @{};
|
||||
return @{ @"genre": genre, @"album": album, @"artist": artist, @"title": title };
|
||||
}
|
||||
|
||||
+ (NSArray *)fileTypes {
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
|
||||
/* Begin PBXFileReference section */
|
||||
833F68411CDBCABC00AFB9F0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
8356BD1B27B469B80074E50C /* HTTPSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPSource.h; path = ../../HTTPSource/HTTPSource.h; sourceTree = "<group>"; };
|
||||
8375B03C17FFEA400092A79F /* OpusPlugin.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OpusPlugin.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8375B03F17FFEA400092A79F /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
8375B04217FFEA400092A79F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
|
@ -122,6 +123,7 @@
|
|||
8375B04517FFEA400092A79F /* Opus */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8356BD1B27B469B80074E50C /* HTTPSource.h */,
|
||||
8384913718081F2700E7332D /* Logging.h */,
|
||||
8375B36D17FFF1FE0092A79F /* Plugin.h */,
|
||||
8375B36A17FFF1CB0092A79F /* OpusDecoder.h */,
|
||||
|
|
|
@ -31,6 +31,11 @@
|
|||
int channels;
|
||||
float frequency;
|
||||
long totalFrames;
|
||||
|
||||
NSString *genre;
|
||||
NSString *album;
|
||||
NSString *artist;
|
||||
NSString *title;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
#import "Logging.h"
|
||||
|
||||
#import "HTTPSource.h"
|
||||
|
||||
@implementation VorbisDecoder
|
||||
|
||||
static const int MAXCHANNELS = 8;
|
||||
|
@ -95,9 +97,92 @@ long sourceTell(void *datasource) {
|
|||
[self willChangeValueForKey:@"properties"];
|
||||
[self didChangeValueForKey:@"properties"];
|
||||
|
||||
genre = @"";
|
||||
album = @"";
|
||||
artist = @"";
|
||||
title = @"";
|
||||
[self updateMetadata];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)updateMetadata {
|
||||
const vorbis_comment *comment = ov_comment(&vorbisRef, -1);
|
||||
|
||||
if(comment) {
|
||||
uint8_t nullByte = '\0';
|
||||
NSString *_genre = genre;
|
||||
NSString *_album = album;
|
||||
NSString *_artist = artist;
|
||||
NSString *_title = title;
|
||||
for(int i = 0; i < comment->comments; ++i) {
|
||||
NSMutableData *commentField = [NSMutableData dataWithBytes:comment->user_comments[i] length:comment->comment_lengths[i]];
|
||||
[commentField appendBytes:&nullByte length:1];
|
||||
NSString *commentString = [NSString stringWithUTF8String:[commentField bytes]];
|
||||
NSArray *splitFields = [commentString componentsSeparatedByString:@"="];
|
||||
if([splitFields count] == 2) {
|
||||
NSString *name = [splitFields objectAtIndex:0];
|
||||
NSString *value = [splitFields objectAtIndex:1];
|
||||
name = [name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
name = [name lowercaseString];
|
||||
if([name isEqualToString:@"genre"]) {
|
||||
_genre = value;
|
||||
} else if([name isEqualToString:@"album"]) {
|
||||
_album = value;
|
||||
} else if([name isEqualToString:@"artist"]) {
|
||||
_artist = value;
|
||||
} else if([name isEqualToString:@"title"]) {
|
||||
_title = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(![_genre isEqual:genre] ||
|
||||
![_album isEqual:album] ||
|
||||
![_artist isEqual:artist] ||
|
||||
![_title isEqual:title]) {
|
||||
genre = _genre;
|
||||
album = _album;
|
||||
artist = _artist;
|
||||
title = _title;
|
||||
[self willChangeValueForKey:@"metadata"];
|
||||
[self didChangeValueForKey:@"metadata"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateIcyMetadata {
|
||||
NSString *_genre = genre;
|
||||
NSString *_album = album;
|
||||
NSString *_artist = artist;
|
||||
NSString *_title = title;
|
||||
|
||||
Class sourceClass = [source class];
|
||||
if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) {
|
||||
HTTPSource *httpSource = (HTTPSource *)source;
|
||||
if([httpSource hasMetadata]) {
|
||||
NSDictionary *metadata = [httpSource metadata];
|
||||
_genre = [metadata valueForKey:@"genre"];
|
||||
_album = [metadata valueForKey:@"album"];
|
||||
_artist = [metadata valueForKey:@"artist"];
|
||||
_title = [metadata valueForKey:@"title"];
|
||||
}
|
||||
}
|
||||
|
||||
if(![_genre isEqual:genre] ||
|
||||
![_album isEqual:album] ||
|
||||
![_artist isEqual:artist] ||
|
||||
![_title isEqual:title]) {
|
||||
genre = _genre;
|
||||
album = _album;
|
||||
artist = _artist;
|
||||
title = _title;
|
||||
[self willChangeValueForKey:@"metadata"];
|
||||
[self didChangeValueForKey:@"metadata"];
|
||||
}
|
||||
}
|
||||
|
||||
- (int)readAudio:(void *)buf frames:(UInt32)frames {
|
||||
int numread;
|
||||
int total = 0;
|
||||
|
@ -112,6 +197,8 @@ long sourceTell(void *datasource) {
|
|||
|
||||
[self willChangeValueForKey:@"properties"];
|
||||
[self didChangeValueForKey:@"properties"];
|
||||
|
||||
[self updateMetadata];
|
||||
}
|
||||
|
||||
do {
|
||||
|
@ -141,6 +228,8 @@ long sourceTell(void *datasource) {
|
|||
|
||||
} while(total != frames && numread != 0);
|
||||
|
||||
[self updateIcyMetadata];
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
|
@ -172,7 +261,7 @@ long sourceTell(void *datasource) {
|
|||
}
|
||||
|
||||
- (NSDictionary *)metadata {
|
||||
return @{};
|
||||
return @{ @"genre": genre, @"album": album, @"artist": artist, @"title": title };
|
||||
}
|
||||
|
||||
+ (NSArray *)fileTypes {
|
||||
|
@ -180,7 +269,7 @@ long sourceTell(void *datasource) {
|
|||
}
|
||||
|
||||
+ (NSArray *)mimeTypes {
|
||||
return @[@"application/ogg", @"application/x-ogg", @"audio/x-vorbis+ogg"];
|
||||
return @[@"application/ogg", @"application/x-ogg", @"audio/ogg", @"audio/x-vorbis+ogg"];
|
||||
}
|
||||
|
||||
+ (float)priority {
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
17C93D340B8FDA66008627D6 /* VorbisDecoder.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = VorbisDecoder.m; sourceTree = "<group>"; };
|
||||
17F562EF0C3BDAAC0019975C /* Vorbis.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Vorbis.xcodeproj; path = ../../Frameworks/Vorbis/macosx/Vorbis.xcodeproj; sourceTree = SOURCE_ROOT; };
|
||||
32DBCF630370AF2F00C91783 /* VorbisPlugin_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VorbisPlugin_Prefix.pch; sourceTree = "<group>"; };
|
||||
8356BD1C27B46A2D0074E50C /* HTTPSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPSource.h; path = ../HTTPSource/HTTPSource.h; sourceTree = "<group>"; };
|
||||
8384913418081A3900E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; };
|
||||
8D5B49B6048680CD000E48DA /* VorbisPlugin.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VorbisPlugin.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8D5B49B7048680CD000E48DA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -111,6 +112,7 @@
|
|||
08FB77AFFE84173DC02AAC07 /* Classes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8356BD1C27B46A2D0074E50C /* HTTPSource.h */,
|
||||
8384913418081A3900E7332D /* Logging.h */,
|
||||
177FCF9D0B90C9530011C3B5 /* Plugin.h */,
|
||||
17C93D330B8FDA66008627D6 /* VorbisDecoder.h */,
|
||||
|
|
Loading…
Reference in New Issue