From d24a3e783ddcdd09bab3e2ab1b041a6b139652a0 Mon Sep 17 00:00:00 2001
From: Matthias Neeracher <neeri@users.sourceforge.net>
Date: Sun, 14 Aug 2011 21:25:48 +0200
Subject: [PATCH] Crude export capabilities work

---
 Medianno/MAAnno.h              |  3 ++
 Medianno/MAAnno.mm             | 10 +++++
 Medianno/MADocWindow.h         |  1 +
 Medianno/MADocWindow.mm        | 78 +++++++++++++++++++++++++++++++---
 Medianno/en.lproj/MainMenu.xib | 34 ++++++++++++++-
 5 files changed, 118 insertions(+), 8 deletions(-)

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 <Foundation/Foundation.h>
 #import <CoreData/CoreData.h>
+#import <QTKit/QTKit.h>
 
 @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 @@
 									<reference key="NSOnImage" ref="1033313550"/>
 									<reference key="NSMixedImage" ref="310636482"/>
 								</object>
+								<object class="NSMenuItem" id="678484874">
+									<reference key="NSMenu" ref="720053764"/>
+									<string key="NSTitle">Export…</string>
+									<string key="NSKeyEquiv">S</string>
+									<int key="NSKeyEquivModMask">1048576</int>
+									<int key="NSMnemonicLoc">2147483647</int>
+									<reference key="NSOnImage" ref="1033313550"/>
+									<reference key="NSMixedImage" ref="310636482"/>
+								</object>
 								<object class="NSMenuItem" id="1010469920">
 									<reference key="NSMenu" ref="720053764"/>
 									<bool key="NSIsDisabled">YES</bool>
@@ -1344,6 +1353,14 @@
 					</object>
 					<int key="connectionID">584</int>
 				</object>
+				<object class="IBConnectionRecord">
+					<object class="IBActionConnection" key="connection">
+						<string key="label">exportMedia:</string>
+						<reference key="source" ref="1014"/>
+						<reference key="destination" ref="678484874"/>
+					</object>
+					<int key="connectionID">592</int>
+				</object>
 			</object>
 			<object class="IBMutableOrderedSet" key="objectRecords">
 				<object class="NSArray" key="orderedObjects">
@@ -1440,6 +1457,7 @@
 							<reference ref="1010469920"/>
 							<reference ref="906148750"/>
 							<reference ref="7029338"/>
+							<reference ref="678484874"/>
 						</object>
 						<reference key="parent" ref="379814623"/>
 					</object>
@@ -2042,6 +2060,11 @@
 						<reference key="parent" ref="0"/>
 						<string key="objectName">AppController</string>
 					</object>
+					<object class="IBObjectRecord">
+						<int key="objectID">590</int>
+						<reference key="object" ref="678484874"/>
+						<reference key="parent" ref="720053764"/>
+					</object>
 				</object>
 			</object>
 			<object class="NSMutableDictionary" key="flattenedProperties">
@@ -2138,6 +2161,7 @@
 					<string>58.IBPluginDependency</string>
 					<string>580.IBPluginDependency</string>
 					<string>583.IBPluginDependency</string>
+					<string>590.IBPluginDependency</string>
 					<string>72.IBPluginDependency</string>
 					<string>73.IBPluginDependency</string>
 					<string>74.IBPluginDependency</string>
@@ -2253,6 +2277,7 @@
 					<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
 					<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
 					<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+					<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
 				</object>
 			</object>
 			<object class="NSMutableDictionary" key="unlocalizedProperties">
@@ -2267,7 +2292,7 @@
 				<reference key="dict.values" ref="0"/>
 			</object>
 			<nil key="sourceID"/>
-			<int key="maxID">589</int>
+			<int key="maxID">592</int>
 		</object>
 		<object class="IBClassDescriber" key="IBDocument.Classes">
 			<object class="NSMutableArray" key="referencedPartialClassDescriptions">
@@ -2307,6 +2332,7 @@
 							<bool key="EncodedWithXMLCoder">YES</bool>
 							<string>addAnnotation:</string>
 							<string>addMediaFiles:</string>
+							<string>exportMedia:</string>
 							<string>mediaSkipBackward:</string>
 							<string>mediaSkipForward:</string>
 							<string>toggleMediaPlay:</string>
@@ -2318,6 +2344,7 @@
 							<string>id</string>
 							<string>id</string>
 							<string>id</string>
+							<string>id</string>
 						</object>
 					</object>
 					<object class="NSMutableDictionary" key="actionInfosByName">
@@ -2326,6 +2353,7 @@
 							<bool key="EncodedWithXMLCoder">YES</bool>
 							<string>addAnnotation:</string>
 							<string>addMediaFiles:</string>
+							<string>exportMedia:</string>
 							<string>mediaSkipBackward:</string>
 							<string>mediaSkipForward:</string>
 							<string>toggleMediaPlay:</string>
@@ -2340,6 +2368,10 @@
 								<string key="name">addMediaFiles:</string>
 								<string key="candidateClassName">id</string>
 							</object>
+							<object class="IBActionInfo">
+								<string key="name">exportMedia:</string>
+								<string key="candidateClassName">id</string>
+							</object>
 							<object class="IBActionInfo">
 								<string key="name">mediaSkipBackward:</string>
 								<string key="candidateClassName">id</string>