diff --git a/Medianno/MAAnno.h b/Medianno/MAAnno.h index ec9493a..59430d3 100644 --- a/Medianno/MAAnno.h +++ b/Medianno/MAAnno.h @@ -8,6 +8,7 @@ #import #import +#import @class MATag; @@ -21,6 +22,8 @@ - (NSString *)shortLocation; + (NSSet *)keyPathsForValuesAffectingShortLocation; +- (QTTime)qtLocation; ++ (NSSet *)keyPathsForValuesAffectingQtLocation; /* * Tags are never manipulated through the tag objects, but always through diff --git a/Medianno/MAAnno.mm b/Medianno/MAAnno.mm index 81c2d90..3ed957c 100644 --- a/Medianno/MAAnno.mm +++ b/Medianno/MAAnno.mm @@ -51,6 +51,16 @@ return [NSSet setWithObject:@"location"]; } +- (QTTime)qtLocation +{ + return QTTimeFromString(self.location); +} + ++ (NSSet *)keyPathsForValuesAffectingQtLocation +{ + return [NSSet setWithObject:@"location"]; +} + - (NSArray *)tagDescriptions { NSSet * tags = self.tags; diff --git a/Medianno/MADocWindow.h b/Medianno/MADocWindow.h index f849332..739dbff 100644 --- a/Medianno/MADocWindow.h +++ b/Medianno/MADocWindow.h @@ -19,6 +19,7 @@ } - (IBAction)addMediaFiles:(id)sender; +- (IBAction)exportMedia:(id)sender; - (void)addMedia:(NSArray *)urls; - (IBAction)addAnnotation:(id)sender; - (IBAction)mediaSkipBackward:(id)sender; diff --git a/Medianno/MADocWindow.mm b/Medianno/MADocWindow.mm index 75856f5..ce166e8 100644 --- a/Medianno/MADocWindow.mm +++ b/Medianno/MADocWindow.mm @@ -37,6 +37,11 @@ #pragma mark Media management +- (QTMovie *)currentMovie +{ + return [movieView movie]; +} + - (IBAction)addMediaFiles:(id)sender { NSOpenPanel * openPanel = [NSOpenPanel openPanel]; @@ -87,10 +92,68 @@ [[[MAAddMediaSheet alloc] init] runWithParentWindow:self media:expandedURLs]; } +- (void)exportMediaToURL:(NSURL *)url +{ + QTMovie * currentMovie = [self currentMovie]; + NSIndexSet * selection = [annotationController selectionIndexes]; + NSArray * annotations = [annotationController arrangedObjects]; + // + // Determine overall range of interest + // + QTTimeRange exportRange = QTMakeTimeRange(QTMakeTimeWithTimeInterval(0.0), [[[currentMovie movieAttributes] valueForKey:QTMovieDurationAttribute] QTTimeValue]); + if ([selection count]) { + if ([selection lastIndex] < [annotations count]-1) + exportRange.duration = [[annotations objectAtIndex:[selection lastIndex]+1] qtLocation]; + if ([selection firstIndex] > 0) { + exportRange.time = [[annotations objectAtIndex:[selection firstIndex]] qtLocation]; + exportRange.duration = QTTimeDecrement(exportRange.duration, exportRange.time); + } + } + NSError * error; + QTMovie * exportMovie = [[QTMovie alloc] initWithMovie:currentMovie timeRange:exportRange error:&error]; + if (!exportMovie) + [[self document] presentError:error]; + + if ([selection count] < [selection lastIndex]-[selection firstIndex]+1) { + [exportMovie setAttribute:[NSNumber numberWithBool:YES] forKey:QTMovieEditableAttribute]; + // + // Cut out discontiguous portions (in reverse order to simplify time accounting) + // + __block NSUInteger lastUsed = [selection lastIndex]; + [selection enumerateRangesWithOptions:NSEnumerationReverse usingBlock:^(NSRange range, BOOL *stop) { + if (range.location+range.length < lastUsed+1) { + QTTimeRange unusedRange = QTMakeTimeRange([[annotations objectAtIndex:range.location+range.length] qtLocation], + [[annotations objectAtIndex:lastUsed] qtLocation]); + unusedRange.duration = QTTimeDecrement(unusedRange.duration, unusedRange.time); + unusedRange.time = QTTimeDecrement(unusedRange.time, exportRange.time); + [exportMovie deleteSegment:unusedRange]; + } + lastUsed = range.location; + }]; + } + // + // Now write out the movie + // + NSDictionary * attributes = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] + forKey:QTMovieFlatten]; + [exportMovie writeToFile:[url path] withAttributes:attributes]; +} + +- (IBAction)exportMedia:(id)sender +{ + NSSavePanel * savePanel = [NSSavePanel savePanel]; + [savePanel setAllowedFileTypes:[QTMovie movieFileTypes:QTIncludeCommonTypes]]; + [savePanel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger result) { + [savePanel orderOut:self]; + if (result == NSFileHandlingPanelOKButton) + [self exportMediaToURL:[savePanel URL]]; + }]; +} + - (IBAction)addAnnotation:(id)sender { [movieView pause:sender]; - QTTime location = [[movieView movie] currentTime]; + QTTime location = [[self currentMovie] currentTime]; MAAnno * anno = [[self document] addAnnotationForMedia:[[mediaController selectedObjects] objectAtIndex:0] location:location]; [annotationController setSelectedObjects:[NSArray arrayWithObject:anno]]; [annotationTable editColumn:[annotationTable columnWithIdentifier:@"tags"] @@ -115,7 +178,7 @@ - (IBAction)toggleMediaPlay:(id)sender { - if ([[movieView movie] rate] > 0.0f) + if ([[self currentMovie] rate] > 0.0f) [movieView pause:sender]; else [movieView play:sender]; @@ -125,7 +188,7 @@ { if ([item action] == @selector(toggleMediaPlay:)) { NSMenuItem * menuItem = (NSMenuItem *)item; - if ([[movieView movie] rate] > 0.0f) + if ([[self currentMovie] rate] > 0.0f) [menuItem setTitle:NSLocalizedString(@"Pause", @"Pause playback")]; else [menuItem setTitle:NSLocalizedString(@"Play", @"Start playback")]; @@ -144,13 +207,14 @@ static NSTimeInterval sLastSkip = 0.0; - (void)skipTimeInterval { - QTTime interval= QTMakeTimeWithTimeInterval(abs(sLastSkip)); - QTTime current = [[movieView movie] currentTime]; + QTMovie * currentMovie = [self currentMovie]; + QTTime interval = QTMakeTimeWithTimeInterval(abs(sLastSkip)); + QTTime current = [currentMovie currentTime]; if (sLastSkip > 0) current = QTTimeIncrement(current, interval); else current = QTTimeDecrement(current, interval); - [[movieView movie] setCurrentTime:current]; + [currentMovie setCurrentTime:current]; sLastSkip *= 1.1; [NSRunLoop cancelPreviousPerformRequestsWithTarget:self selector:@selector(resetSkipFactor:) object:self]; [self performSelector:@selector(resetSkipFactor:) withObject:self afterDelay:1.0]; @@ -214,7 +278,7 @@ static NSTimeInterval sLastSkip = 0.0; NSArray * selection = [annotationController selectedObjects]; if ([selection count]) if (MAAnno * firstSelectedAnno = [selection objectAtIndex:0]) - [[movieView movie] setCurrentTime:QTTimeFromString([firstSelectedAnno location])]; + [[self currentMovie] setCurrentTime:[firstSelectedAnno qtLocation]]; } @end diff --git a/Medianno/en.lproj/MainMenu.xib b/Medianno/en.lproj/MainMenu.xib index 4293527..6f32b69 100644 --- a/Medianno/en.lproj/MainMenu.xib +++ b/Medianno/en.lproj/MainMenu.xib @@ -293,6 +293,15 @@ + + + Export… + S + 1048576 + 2147483647 + + + YES @@ -1344,6 +1353,14 @@ 584 + + + exportMedia: + + + + 592 + @@ -1440,6 +1457,7 @@ + @@ -2042,6 +2060,11 @@ AppController + + 590 + + + @@ -2138,6 +2161,7 @@ 58.IBPluginDependency 580.IBPluginDependency 583.IBPluginDependency + 590.IBPluginDependency 72.IBPluginDependency 73.IBPluginDependency 74.IBPluginDependency @@ -2253,6 +2277,7 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin @@ -2267,7 +2292,7 @@ - 589 + 592 @@ -2307,6 +2332,7 @@ YES addAnnotation: addMediaFiles: + exportMedia: mediaSkipBackward: mediaSkipForward: toggleMediaPlay: @@ -2318,6 +2344,7 @@ id id id + id @@ -2326,6 +2353,7 @@ YES addAnnotation: addMediaFiles: + exportMedia: mediaSkipBackward: mediaSkipForward: toggleMediaPlay: @@ -2340,6 +2368,10 @@ addMediaFiles: id + + exportMedia: + id + mediaSkipBackward: id