All:
I am recording a movie, using AVCaptureMovieFileOutput. As various events occur, I wish to store the event's title/time in the QuickTime movie being written. Thus I might have 20-30 data points that I wish to associate with a particular movie.
My strategy is to use metadata, but I have not been having much luck. Can someone please tell me, first of all:
a) Can I store arbitrary metadata, or just those keys and values as defined in AVMetadataFormat.h? I would like to be able to store an array.
b) If I can store an arbitrary array, what key does the trick? If not, could I store my metadata in a comment field (ugly, but I could parse 20-30 points quickly enough).
c) The code shown below does not appear to work, as no matter what I put in for the item.key (AVMetadataQuickTimeMetadataKeyArtist, AVMetadataCommonKeyArtist, or all sorts of other things ending in Artist) I never see anything in iTune's Get Info window.
- (IBAction)recordEvent:(id)sender {
NSLog(#"Record a metadata point here ...");
// is there any metadata associated with the file yet?
NSArray * existingMetaData = self.aMovieFileOutput.metadata;
NSMutableArray * newMetadataArray = nil;
if(existingMetaData){
newMetadataArray = [existingMetaData mutableCopy];
} else {
newMetadataArray = [[NSMutableArray alloc]init];
}
AVMutableMetadataItem * item = [[AVMutableMetadataItem alloc]init];
item.keySpace = AVMetadataKeySpaceCommon;
item.key = AVMetadataQuickTimeMetadataKeyArtist;
item.value = #"Enya, really!"; // in practice this will be the title of (UIButton *)sender
item.time = CMTimeMake(0.0,1.0);
[newMetadataArray addObject:item];
self.aMovieFileOutput.metadata = newMetadataArray;
}
Any advice would be greatly appreciated.
Thanks!
Storing metadata in QuickTime file via AVCaptureMovieFileOutput & AVMutableDataItems only allows you to store values for keys predefined in AVMetadataKeySpaceCommon keyspace, i. e. AVMetadataCommonKey* like keys
All the other data is ignoring
Related
I’m trying to attach some XMP metadata to a QuickTime video I'm exporting using AVAssetExportSession.
AVFoundation does support writing metadata (AVMetadataItem) and I’ve managed to export simple values which can subsequently be examined using exiftool:
AVMutableMetadataItem *item = [AVMutableMetadataItem metadataItem];
item.identifier = [AVMetadataItem identifierForKey:AVMetadataCommonKeyTitle keySpace:AVMetadataKeySpaceCommon];
item.value = #"My Title";
exportSession.metadata = #[item];
But I’m having trouble configuring my AVMetadataItem’s to correctly encode XMP. According to the Adobe XMP spec, XMP in QuickTime videos should be under the moov / udta / XMP_ atoms but I can’t see a way to make hierarchical metadata using the AVFoundation API, or any key space that corresponds to this part of the metadata.
I also need to write XMP metadata to images, and Image I/O does have direct support for this (CGImageMetadataCreateFromXMPData), but I can't find anything equivalent in AVFoundation.
If it's not possible using AVFoundation (or similar), I'll probably look at integrating XMP-Toolkit-SDK but this feels like a clunky solution when AVFoundation almost seems to do what I need.
I finally managed to figure this after trying lots of variations of keys/key spaces and other attributes of AVMetadataItem:
Use a custom XMP_ key in the AVMetadataKeySpaceQuickTimeUserData key space
Set the value not as an NSString but as an NSData containing UTF-8 data for the payload
Set the dataType to raw data
This results in XMP attributes that can be read by exiftool as expected.
NSString *payload =
#"<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"MyAppXMPLibrary\">"
"<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">"
"<rdf:Description rdf:about=\"\" xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\">"
"<xmp:CreatorTool>My App</xmp:CreatorTool>"
"</rdf:Description>"
"</rdf:RDF>"
"</x:xmpmeta>";
NSData *data = [payload dataUsingEncoding:kCFStringEncodingUTF8];
AVMutableMetadataItem *item = [AVMutableMetadataItem metadataItem];
item.identifier = [AVMetadataItem identifierForKey:#"XMP_"
keySpace:AVMetadataKeySpaceQuickTimeUserData];
item.dataType = (NSString *)kCMMetadataBaseDataType_RawData;
item.value = data;
exportSession.metadata = #[item];
I try to save many objects in coredata, but get this crash:
Communications error: <OS_xpc_error: <error: 0x19b354af0> { count = 1, contents =
"XPCErrorDescription" => <string: 0x19b354e50> { length = 22, contents = "Connection interrupted" }
}>
Message from debugger: Terminated due to memory issue
I use MagicalRecord:
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){
for (int i = 0; i < json.count; i++) {
[Product parseWithData:((NSMutableArray *)json)[i]];
}
}];
Product.m
+ (void)parseWithData:(NSDictionary *)dictionary {
NSString *xml_id = [dictionary[#"XML_ID"] isKindOfClass:[NSString class]] ? dictionary[#"XML_ID"] : #"";
Product *product = [Product getProductWithXML_id:xml_id];
if (!product)
product = [Product MR_createEntity];
product.xml_id = xml_id;
product.code = [dictionary[#"Code"] isKindOfClass:[NSString class]] ? dictionary[#"Code"] : #"";
...
}
Can you suggest me, how can i save it?
When i save my objects in loop to core data - memory grow very fast
It seems to be a memory issue.
Try surrounding the inner part of your for loop with
autoreleasepool {
...
}
You need to paginate the way you get the data and/or save it.
By paginate, i mean :
download the first 1000 (for example, it depends on the content really)
When its done, save the 1000 you just go
When that is done, get the next 1000, save it again, and so on.
You need to know the number you're trying to get and use the (if I remember correctly ) SetLimit: on the parse method, and SetSkip. The skip skips the X first elements, and the limit is the max number of items that will be downloaded.
That way, you skip 0 with limit 1000, then recall the method with skip += limit, and you'll get the second 1000 chunk, and so on. The last chunk will obviously be smaller than 1000.
Doing this will drastically increase the time taken, but that could be done seamlessly in the background ; but it will be spread over much enough to need less memory.
Do it, and see if it makes a big difference. If not, you could always reduce to 500 instead of 1000, or completely change your architecture ; maybe you don't even need ALL the items right now !
I've been having this bug for several weeks already. I searched on many forums every reply on duplicates and I implemented some of the normal approaches and still it doesn't work properly.
So to give you some context I'm working on a recipe application that scraps html recipes from the web and stores it in core data, simple right? Well when the client asked adding support for iCloud Sync I though it was going to be easy specially working on iOS 7 only which solves most of the problems for you.
The problems arises when the app populates initial data in the application. I have two related entities called MainCategory[e1] and Category[e2], there is one to many relationship between them (e1 <->>> e2).
The first the app starts it will create 5 Main Categories and for each Main Category it will add 5 Categories
+ (BOOL)initialLoad
{
DLog(#"Initial Load");
//Create main and sub categories to database
NSDictionary * categoriesDic = #{
CAT_MEAL_TYPE: #[C_STARTER,C_MAINS,C_DESSERT,C_SOUPS,C_SALAD],
CAT_INGREDIENT: #[C_BEEF,C_CHICKEN,C_PASTA,C_SALMON,C_CHOCOLATE],
CAT_CUISINE : #[C_CHINESE,C_FRENCH,C_INDIAN,C_ITALIAN,C_MOROCCAN],
CAT_SEASON : #[C_CHRISTMAS,C_SUNDAY_ROAST,C_DINNER,C_BBQ,C_NIBBLES],
CAT_DIET : #[C_WHEATFREE,C_VEGETARIAN,C_LOW_FAT,C_LOW_GI,C_DAIRY_FREE]
};
NSArray * mainCategoryKeys = #[CAT_MEAL_TYPE,CAT_INGREDIENT,CAT_CUISINE,CAT_SEASON,CAT_DIET];
for(NSString * eachMainCategoryName in mainCategoryKeys)
{
//Create Main category
MainCategory * eachMainCategory = [MainCategory mainCategoryWithName:eachMainCategoryName];
NSArray * subCategories = [categoriesDic objectForKey:eachMainCategoryName];
//Create Sub categories and adds them to main category
for(NSString * eachCategoryName in subCategories)
{
/*Category got renamed to zCategory given it's a reserver name in the framework and
can not be used */
zCategory * eachCategory = [zCategory categoryWithName:eachCategoryName];
[eachMainCategory addCategoriesObject:eachCategory];
}
}
[((AppDelegate *)[UIApplication sharedApplication].delegate) saveContext];
return TRUE;
}`
Then after saving the context all this initial data will sync with the database in iCloud, so far so good. The problem comes when on the second device it runs the same initialLoad code and gets sync once again. The result is getting double MainCategories and Categories as many of you know this problem.
After reading several threads about how to remove them I used the dateCreated approach where you add a NSDate property to each entity so every time you create one instance it will have a timestamp to track which one is older and which one is newer. Then I simply add an observer from NSNotificationCenter checking the iCloud import notification NSPersistentStoreCoordinatorStoresDidChangeNotification and runs a timerCheck that after 5 seconds will execute on the mainThread a clean duplicates method.
- (void)checkTimer{
if(self.cleanTimer)
{
[self.cleanTimer invalidate];
self.cleanTimer = nil;
}//schedule timer to clean iCloud duplicates of database
self.cleanTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(cleanDuplicates:) userInfo:nil repeats:FALSE];
}
- (void)cleanDuplicates:(NSTimer*)timer{
[self performSelectorOnMainThread:#selector(cleanCron) withObject:nil waitUntilDone:TRUE];}
I'm invalidating the timer every time checkTimer method gets call in order to restart it again because you normally get several NSPersistentStoreCoordinatorStoresDidChangeNotification when content gets updated/inserted/deleted, this way I know it will run once after all the notifications have gone through.
btw cleanCron just calls a class method cleanDuplicates
- (void)cleanCron
{
[CTFetchCoreData cleanDuplicates];
}`
Here is where the non magic happens, I get all the MainCategories which will be 10 given they have been duplicated and order them with the oldest ones at the beginning, then it iterates and save them in an dictionary with their name as the key so whenever it finds another MainCategory with the same name it just deletes it. Btw in the relationship e1<->>e2 there is a cascade delete rule so every time you delete a MainCategory item it deletes all the related Categories with it so there shouldn't be a problem.
+ (BOOL)cleanDuplicates
{
#synchronized(self){
//Fetch mainCategories from coreData
NSArray * mainCategories = [CTFetchCoreData fetchAllMainCategories];
// Clean duplicate Main Categories
NSMutableDictionary * uniqueMainCatDic = [NSMutableDictionary dictionary];
// Sorts the array with the oldest dateCreated one
mainCategories = [mainCategories sortedArrayUsingComparator:^NSComparisonResult(MainCategory* obj1,MainCategory * obj2) {
if(obj1.dateCreated == nil || obj2.dateCreated == nil)
{
DLog(#"ERROR Date Created");
}
return [obj1.dateCreated compare:obj2.dateCreated];
}];
// if there are more than five MainCategories it procedes the clenaup
if(mainCategories.count > 5)
{
for(MainCategory* eachMainCat in mainCategories)
{
MainCategory * originalMainCat = [uniqueMainCatDic objectForKey:eachMainCat.name];
if( originalMainCat == nil)
{
DLog(#"-> %# = %#",eachMainCat.name, eachMainCat.dateCreated);
[uniqueMainCatDic setObject:eachMainCat forKey:eachMainCat.name];
}else{
// Clean duplicate Categories
[[self managedObjectContext] deleteObject:eachMainCat];
DLog(#"x %# = %#",eachMainCat.name, eachMainCat.dateCreated);
}
}
DLog(#"Cleaning Main Categories");
}
}
[[AppDelegate sharedInstance] saveContext];
return TRUE;
}
It turns out that after I run it on the second device I will get this output :
Sesame[4145:60b] -> Cuisine = 2014-02-06 16:15:38 +0000
Sesame[4145:60b] -> Meal = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] x Meal = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] -> Ingredients = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] x Ingredients = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] x Cuisine = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] x Cuisine = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] -> Occasion = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] -> Diet = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] x Diet = 2014-02-06 17:15:54 +0000
which means that the same MainCategories are getting deleted, they have the same timestamp! I'm wondering how iCloud gets the information merged.
Please if you know a better approach to clean duplicated apart from the dateCreated property please tell me because I've tried it a lot without luck, there should be a better approach.
Thanks in advance!
Update :
Finally I've managed to solve my problem after all, crazy as it sounds I was getting duplicate instances from iCloud! that's what the dates were the same. I just added an if to check if both dates are the same then don't delete the MainCategory, and so next time you open your app Core Data will refix the merge and update the database with the correct instances and different date values as it was supposed to be.
I can't see anything obviously wrong with your code, though I would recommend using UUIDs instead of dates for ordering your duplicates. But that is unlikely to be related to what you are seeing.
To be honest, it looks to me that Core Data is really messing things up. (Eg It also seems there are 3 Cuisine categories.)
I experienced this type of issue when working with Core Data sync if I tried to delete the cloud data files, and didn't give it time to thoroughly remove the files from all devices. You end up with old transaction logs in there, which trigger extra objects to be inserted.
Core Data also tries to handle all merging on its own. How that happens is anyone's guess.
Core Data + iCloud is a bit unusual in that it is one of the only sync frameworks which has no concept of global identity. There are actually good reasons for Apple not doing it, which are too subtle to discuss here, but it does make it difficult for developers. Deduping post-merge is an ugly solution IMO. Your store has to become invalid before it can become valid again.
I much prefer the approach of frameworks like Wasabi Sync, TICDS, and Ensembles, which all have a concept of global identity and do not require deduping as a result.
(Disclosure: I founded and develop the Ensembles framework)
Also avoid using this
NSMutableDictionary * uniqueMainCatDic = [NSMutableDictionary dictionary];
rather use
NSMutableDictionary * uniqueMainCatDic = [[NSMutableDictionary alloc] init];
I think your weirdness of duplicates may go away if you always alloc the mutable dictionary. It took me weeks to figure this out - not sure if its a bug.
i am implementing speech to text by OpenEars feature in my app.
i am also using Rejecto plugin to make the recognition better and RapidEars for faster results. the goal is to detect phrase and single words, for example :
lmGenerator = [[LanguageModelGenerator alloc] init];
NSArray *words = [NSArray arrayWithObjects:#"REBETANDEAL",#"NEWBET",#"REEEBET", nil];
NSString *name = #"NameIWantForMyLanguageModelFiles";
NSError *err = [lmGenerator generateRejectingLanguageModelFromArray:words
withFilesNamed:name
withOptionalExclusions:nil
usingVowelsOnly:FALSE
withWeight:nil
forAcousticModelAtPath:[AcousticModel pathToModel:#"AcousticModelEnglish"]]; // Change "AcousticModelEnglish" to "AcousticModelSpanish" to create a Spanish Rejecto model.
// Change "AcousticModelEnglish" to "AcousticModelSpanish" to create a Spanish language model instead of an English one.
NSDictionary *languageGeneratorResults = nil;
NSString *lmPath = nil;
NSString *dicPath = nil;
if([err code] == noErr) {
languageGeneratorResults = [err userInfo];
lmPath = [languageGeneratorResults objectForKey:#"LMPath"];
dicPath = [languageGeneratorResults objectForKey:#"DictionaryPath"];
} else {
NSLog(#"Error: %#",[err localizedDescription]);
}
// Change "AcousticModelEnglish" to "AcousticModelSpanish" to perform Spanish recognition instead of English.
[self.pocketsphinxController setRapidEarsToVerbose:FALSE]; // This defaults to FALSE but will give a lot of debug readout if set TRUE
[self.pocketsphinxController setRapidEarsAccuracy:10]; // This defaults to 20, maximum accuracy, but can be set as low as 1 to save CPU
[self.pocketsphinxController setFinalizeHypothesis:TRUE]; // This defaults to TRUE and will return a final hypothesis, but can be turned off to save a little CPU and will then return no final hypothesis; only partial "live" hypotheses.
[self.pocketsphinxController setFasterPartials:TRUE]; // This will give faster rapid recognition with less accuracy. This is what you want in most cases since more accuracy for partial hypotheses will have a delay.
[self.pocketsphinxController setFasterFinals:FALSE]; // This will give an accurate final recognition. You can have earlier final recognitions with less accuracy as well by setting this to TRUE.
[self.pocketsphinxController startRealtimeListeningWithLanguageModelAtPath:lmPath dictionaryAtPath:dicPath acousticModelAtPath:[AcousticModel pathToModel:#"AcousticModelEnglish"]]; // Starts the rapid recognition loop. Change "AcousticModelEnglish" to "AcousticModelSpanish" in order to perform Spanish language recognition.
[self.openEarsEventsObserver setDelegate:self];
most of the time the result is fine, but sometime it makes a mix from separate strings objects. for example i pass the words array : #[#"ME AND YOU",#"YOU",#"ME"] and the output can be : "YOU ME ME ME AND". i dont want it to recognize only part of a phrase.
any ideas please?
On the pocketsphinxDidReceiveHypothesis:(NSString *)hypothesis recognitionScore:(NSString *)recognitionScore utteranceID:(NSString *)utteranceID you can check if the hypothesis is in your words array before showing it.
- (void) pocketsphinxDidReceiveHypothesis:(NSString *)hypothesis recognitionScore:(NSString *)recognitionScore utteranceID:(NSString *)utteranceID {
if ([words containsObject:hypothesis]) {
//show hypothesis
}
}
OpenEars developer here. To detect fixed phrases using OpenEars, use the new dynamic grammar generator method of LanguageModelGenerator to create a rules-based grammar dynamically rather than a statistical language model: http://www.politepix.com/2014/04/10/openears-1-7-introducing-dynamic-grammar-generation/
being that PDFKit is not available on iOS, how is it possible to get the outline of a pdf document in that environment? Is commercial libraries like FastPdfKit or PSPDFKit the only solution?
It's not TOO tricky to access the pdf outline. My outline parser has about 420 LOC. I'll post some snippets, so you'll get the idea. I can't post the full code as it's a commercial library.
You basically start like this:
CGPDFDictionaryRef outlineRef;
if(CGPDFDictionaryGetDictionary(pdfDocDictionary, "Outlines", &outlineRef)) {
going down to
NSArray *outlineElements = nil;
CGPDFDictionaryRef firstEntry;
if (CGPDFDictionaryGetDictionary(outlineRef, "First", &firstEntry)) {
NSMutableArray *pageCache = [NSMutableArray arrayWithCapacity:CGPDFDocumentGetNumberOfPages(documentRef)];
outlineElements = [self parseOutlineElements:firstEntry level:0 error:&error documentRef:documentRef cache:pageCache];
}else {
PSPDFLogWarning(#"Error while parsing outline. First entry not found!");
}
you parse single items like this:
// parse title
NSString *outlineTitle = stringFromCGPDFDictionary(outlineElementRef, #"Title");
PSPDFLogVerbose(#"outline title: %#", outlineTitle);
if (!outlineTitle) {
if (error_) {
*error_ = [NSError errorWithDomain:kPSPDFOutlineParserErrorDomain code:1 userInfo:nil];
}
return nil;
}
NSString *namedDestination = nil;
CGPDFObjectRef destinationRef;
if (CGPDFDictionaryGetObject(outlineElementRef, "Dest", &destinationRef)) {
CGPDFObjectType destinationType = CGPDFObjectGetType(destinationRef);
The most annoying thing is that you have Named Destinations in most pdf documents, which need additional steps to resolve. I save those in an array and resolve them later.
It took quite a while to "get it right" as there are LOTS of differences in the PDFs that are around, and even if you implement everything in compliance to the PDF reference, some files won't work until you apply further tweaking. (PDF is a mess!)
It is now possible in iOS 11+.
https://developer.apple.com/documentation/pdfkit
You can get the PDFOutline of a PDFDocument.
The PDFOutline's outlineRoot will return outline items if there are any and NULL if none.