1 // Copyright (c) 2009 Yanagi Asakura
3 // This software is provided 'as-is', without any express or implied
4 // warranty. In no event will the authors be held liable for any damages
5 // arising from the use of this software.
7 // Permission is granted to anyone to use this software for any purpose,
8 // including commercial applications, and to alter it and redistribute it
9 // freely, subject to the following restrictions:
11 // 1. The origin of this software must not be misrepresented; you must not
12 // claim that you wrote the original software. If you use this software
13 // in a product, an acknowledgment in the product documentation would be
14 // appreciated but is not required.
16 // 2. Altered source versions must be plainly marked as such, and must not be
17 // misrepresented as being the original software.
19 // 3. This notice may not be removed or altered from any source
26 // Created by 柳 on 09/09/12.
27 // Copyright 2009 __MyCompanyName__. All rights reserved.
30 #import "ElisController.h"
33 static float convertQTTimeToSecond(QTTime t)
35 return (float)t.timeValue/t.timeScale;
38 @implementation ElisController
42 layers = [[NSMutableArray alloc] init];
43 _animationLayerFactory = [[ElisAnimationLayerFactory alloc] init];
49 ProjectMovieSize = CGRectMake(0, 0, 640, 480);
52 // [ElisCustomFilter class];
54 NSLog(@"Building effects ...");
56 [self buildEffectMenu];
58 // Quartz Composerのデフォルト再生時間を変更。
59 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
60 [defaults setInteger:60*10 forKey:@"QuartzComposerDefaultMovieDuration"];
65 #ifdef __SNOW_LEOPARD_GCD__
66 diq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
70 - (CALayer*)createNewLayer:(NSString*)path
75 layer = [[ElisLayer alloc] init];
77 NSWorkspace* sharedWorkspace = [NSWorkspace sharedWorkspace];
80 if([sharedWorkspace type:[sharedWorkspace typeOfFile:path error:nil]
81 conformsToType:@"public.image"])
83 m = [[ElisMedia alloc] initWithImageFile:path];
84 alayer = [_animationLayerFactory createNewAnimationLayer:convertQTTimeToSecond([m duration])
85 name:[path lastPathComponent] type:@"image"];
87 else if([sharedWorkspace type:[sharedWorkspace typeOfFile:path error:nil]
88 conformsToType:@"public.audio"])
90 m = [[ElisMedia alloc] initWithSoundFile:path];
91 alayer = [_animationLayerFactory createNewAnimationLayer:convertQTTimeToSecond([m duration])
92 name:[path lastPathComponent] type:@"sound"];
94 else if([sharedWorkspace type:[sharedWorkspace typeOfFile:path error:nil]
95 conformsToType:@"public.movie"] ||
96 [sharedWorkspace type:[sharedWorkspace typeOfFile:path error:nil]
97 conformsToType:@"com.apple.quartz-composer-composition"])
99 m = [[ElisMedia alloc] initWithMovieFile:path];
100 alayer = [_animationLayerFactory createNewAnimationLayer:convertQTTimeToSecond([m duration])
101 name:[path lastPathComponent] type:@"movie"];
104 NSLog(@"error: cannot open %@", path);
109 // [alayer setValue:layer forKey:@"ElisLayer"];
111 [layer setAlayer:alayer];
112 [layers addObject:layer];
117 - (CALayer*)createNewTextLayer:(NSString*)t
119 // [_textLayerField selectAll:nil];
120 // NSAttributedString* as = [[NSAttributedString alloc]
121 // initWithRTF:[_textLayerField RTFFromRange:[_textLayerField selectedRange]] documentAttributes:nil];
122 NSAttributedString* as = [_textLayerField attributedString];
123 ElisLayer* layer = [[ElisLayer alloc] init];
124 ElisMedia* m = [[ElisMedia alloc] initWithText:as];
125 CALayer* cal = [_animationLayerFactory createNewAnimationLayer:convertQTTimeToSecond([m duration])
126 name:@"text" type:@"text"];
129 [layer setAlayer:cal];
130 [layers addObject:layer];
135 // 絶対時間qttimeと関係があるレイヤーをまとめて返す。
136 - (void)getFrameForTime:(QTTime)qttime result:(NSMutableArray*)layerSet
138 int size = [layers count];
141 if(convertQTTimeToSecond(qttime) >= hipTime){
142 [_mainView stopDisplayLink];
144 [_playstopButton setState:NSOffState];
148 globalCurrentTime = qttime;
149 [self moveSliderTo:qttime];
150 [_tableController reload];
152 // GCD使ってみたら画面がちらつく。どういうことなの...?
153 // [array addObject] ってどう見てもクリティカルセッションだった。
154 #ifdef __SNOW_LEOPARD_GCD__
155 void** temp = malloc(sizeof(void*) * 128);
156 memset(temp, 0, sizeof(void*)*128);
158 dispatch_apply(size, diq, ^(size_t i) {
159 ElisLayer* l = [layers objectAtIndex:i];
160 if([l isInclude:qttime]){
161 if(playing) [l play];
162 // [layerSet addObject:l];
165 if(playing) [l stop];
170 for(i = 0; i < 128; i++)
171 if(temp[i] != 0) [layerSet addObject:temp[i]];
174 // GCDなりOpenMPなりで並列化すること。
177 for(i = 0; i < size; i++){
178 l = [layers objectAtIndex:i];
179 if([l isInclude:qttime]){
180 if(playing) [l play];
181 [layerSet addObject:l];
183 if(playing) [l stop];
189 - (void)play:(QTTime)time
191 NSMutableArray* interestLayers = [[NSMutableArray alloc] init];
192 int i, size = [layers count];
194 hipTime = [self getHipTime];
196 for(i = 0; i < size; i++)
197 if([[layers objectAtIndex:i] isInclude:time])
198 [interestLayers addObject:[layers objectAtIndex:i]];
200 size = [interestLayers count];
202 for(i = 0; i < size; i++)
203 [(ElisLayer*)[interestLayers objectAtIndex:i] play];
208 - (void)stop:(QTTime)time
210 // NSMutableArray* interestLayers = [[NSMutableArray alloc] init];
211 int i, size = [layers count];
213 // hipTime = [self getHipTime];
215 // for(i = 0; i < size; i++)
216 //// if([[layers objectAtIndex:i] isInclude:time])
217 // [interestLayers addObject:[layers objectAtIndex:i]];
219 // size = [interestLayers count];
221 // for(i = 0; i < size; i++)
222 // [(ElisLayer*)[interestLayers objectAtIndex:i] stop];
224 for(i = 0; i < size; i++)
225 [(ElisLayer*)[layers objectAtIndex:i] stop];
228 globalCurrentTime = time;
232 - (IBAction)startPlay:(id)sender
234 hipTime = [self getHipTime];
235 QTTime currentTime = QTMakeTime([timeSlider floatValue] * hipTime * DEFAULT_FPS, DEFAULT_FPS);
236 [self seek:currentTime];
237 [self play:currentTime];
238 [_mainView startDisplayLink];
241 - (IBAction)stopPlay:(id)sender
243 hipTime = [self getHipTime];
244 QTTime currentTime = QTMakeTime([timeSlider floatValue] * hipTime * DEFAULT_FPS, DEFAULT_FPS);
245 [self stop:currentTime];
246 [_mainView stopDisplayLink];
251 int i, size = [layers count];
252 float hipTimeSecond = 0.0f, candidate;
254 for(i = 0; i < size; i++){
255 candidate = convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time)
256 + convertQTTimeToSecond([[layers objectAtIndex:i] mapping].duration);
257 if(candidate > hipTimeSecond)
258 hipTimeSecond = candidate;
261 return hipTimeSecond;
264 - (void)moveSliderTo:(QTTime)time
266 float now = convertQTTimeToSecond(time);
267 [self performSelectorOnMainThread:@selector(moveSliderOnThread:) withObject:[NSNumber numberWithFloat:now] waitUntilDone:NO];
268 // [timeSlider setFloatValue:now/hipTime];
269 // [timeCodeField setStringValue:QTStringFromTime(QTMakeTime(now * DEFAULT_FPS, DEFAULT_FPS))];
270 // [_timeLineController movePlaybackBar:now*timeLineScale];
273 - (void)moveSliderOnThread:(NSNumber*)nowv
275 float now = [nowv floatValue];
276 [timeSlider setFloatValue:now/hipTime];
277 [timeCodeField setStringValue:QTStringFromTime(QTMakeTime(now * DEFAULT_FPS, DEFAULT_FPS))];
278 [_timeLineController movePlaybackBar:now*timeLineScale];
281 - (IBAction)timeSliderChanged:(id)sender
283 float seconds = [sender floatValue];
284 QTTime currentTime = QTMakeTime(seconds * hipTime * DEFAULT_FPS, DEFAULT_FPS);
285 [timeCodeField setStringValue:QTStringFromTime(currentTime)];
286 _currentTime = currentTime;
287 globalCurrentTime = currentTime;
291 - (void)seek:(QTTime)time
293 int i, size = [layers count];
294 [_mainView seek:time];
296 for(i = 0; i < size; i++)
297 [[layers objectAtIndex:i] seek:time];
303 hipTime = [self getHipTime];
304 [_mainView getFrameForQTTime:_currentTime];
307 - (void)getSoundTrack:(NSMutableArray*)soundTrack
309 int i, size = [layers count];
310 for(i = 0; i < size; i++)
311 [[layers objectAtIndex:i] getSoundTrack:soundTrack];
314 - (IBAction)deleteSelectLayer:(id)sender
316 CALayer* selected = [_timeLineController getSelectLayer];
317 ElisLayer* layer = [selected valueForKey:@"ElisLayer"];
320 [layers removeObject:layer];
321 [selected removeFromSuperlayer];
322 [_timeLineController removeSelectLayer];
323 [_tableController createPropertyTable:nil];
324 [_tableController reload];
328 - (IBAction)recordingStateChanged:(id)sender
330 recording = !recording;
333 - (IBAction)removeAllKeyFrame:(id)sender
335 [_tableController removeAllKeyframe];
339 - (IBAction)removeEffect:(id)sender
341 [_tableController removeEffect];
345 - (IBAction)writeToFile:(id)sender
347 NSSavePanel* sp = [NSSavePanel savePanel];
349 [sp setRequiredFileType:@"mov"];
350 [sp beginSheetForDirectory:nil
352 modalForWindow:_mainWindow
354 didEndSelector:@selector(writeMovie:returnCode:contextInfo:)
358 - (void)writeMovie:(NSSavePanel*)sheet returnCode:(int)code contextInfo:(void*)info
360 NSString* path = [sheet filename];
362 if(code == NSCancelButton) return;
365 ElisWriterLegacy* writer;
366 writer = [[ElisWriterLegacy alloc] init];
367 [writer setMainWindow:_mainWindow];
368 [writer setMainController:self];
369 [writer setMainView:_mainView];
371 // [_writer write:sheet];
372 [NSThread detachNewThreadSelector:@selector(write:) toTarget:writer withObject:sheet];
376 - (void)buildEffectMenu
378 NSArray* filterNames;
381 filterNames = [CIFilter filterNamesInCategories:[NSArray arrayWithObjects:
382 kCICategoryDistortionEffect,
383 kCICategoryGeometryAdjustment,
384 kCICategoryCompositeOperation,
385 kCICategoryHalftoneEffect,
386 kCICategoryColorAdjustment,
387 kCICategoryColorEffect,
388 kCICategoryTransition,
389 kCICategoryTileEffect,
390 kCICategoryGenerator,
400 NSMenuItem* item = [[NSMenuItem alloc] init], *child;
401 NSMenu* menu = [[NSMenu alloc] init];
404 [item setTitle:@"Effect"];
405 [menu setTitle:@"Effect"];
407 filterNames = [CIFilter filterNamesInCategory:kCICategoryBuiltIn];
409 for(name in filterNames){
410 filter = [CIFilter filterWithName:name];
411 attrs = [filter attributes];
412 inputKeys = [filter inputKeys];
413 if([inputKeys count] == 1) goto jimp;
414 for(elm in inputKeys){
415 if([elm isEqualToString:@"inputImage"]) continue;
416 if([[[attrs valueForKey:elm] valueForKey:kCIAttributeClass] isEqualToString:@"CIVector"]){
417 if([[[attrs valueForKey:elm] valueForKey:kCIAttributeDefault] count] == 2) goto ok;
419 if(!([[[attrs valueForKey:elm] valueForKey:kCIAttributeClass] isEqualToString:@"NSNumber"] ||
420 [[[attrs valueForKey:elm] valueForKey:kCIAttributeClass] isEqualToString:@"CIColor"])){
425 child = [[[NSMenuItem alloc] init] autorelease];
426 [child setTitle:name];
427 [child setAction:@selector(effectMenuPushed:)];
428 [child setTarget:self];
431 [menu addItem:child];
434 ; // ラベルの後ろには必ずstatementがないといけないらしい。なんで?
436 NSLog(@"%d effetcs usable.", c);
437 [item setSubmenu:menu];
438 [menu setAutoenablesItems:NO];
439 [item setEnabled:YES];
441 [[NSApp mainMenu] insertItem:item atIndex:5];
442 [item setTarget:self];
445 - (void)effectMenuPushed:(id)sender
448 l = [_timeLineController getSelectLayer];
449 [[l valueForKey:@"ElisLayer"] addEffect:[sender title]];
451 [_tableController createPropertyTable:[l valueForKey:@"ElisLayer"]];
452 [_undoManager pushOperation:[l valueForKey:@"ElisLayer"]];
453 [_tableController reload];
456 - (void)saveProjectToFile:(NSSavePanel*)sheet returnCode:(int)code contextInfo:(void*)info
458 NSString* path = [sheet filename];
459 NSMutableData* data = [NSMutableData data];
460 NSKeyedArchiver* encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
462 [encoder encodeObject:layers forKey:@"layers"];
463 [encoder encodeFloat:ProjectMovieSize.size.width forKey:@"movieWidth"];
464 [encoder encodeFloat:ProjectMovieSize.size.height forKey:@"movieHeight"];
465 [encoder encodeObject:ELIS_VERSION forKey:@"version"];
466 [encoder finishEncoding];
470 [data writeToFile:path atomically:YES];
473 - (void)loadProjectFromFile:(NSString*)path
475 NSMutableData* data = [NSMutableData dataWithContentsOfFile:path];
476 NSKeyedUnarchiver* decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
479 [_timeLineController awakeFromNib]; // これはひどい。
480 NSString* version = [decoder decodeObjectForKey:@"version"];
481 if([version compare:ELIS_VERSION] > 0) return;
482 layers = [decoder decodeObjectForKey:@"layers"];
483 w = [decoder decodeFloatForKey:@"movieWidth"];
484 h = [decoder decodeFloatForKey:@"movieHeight"];
485 [decoder finishDecoding];
487 ProjectMovieSize.size.width = w;
488 ProjectMovieSize.size.height = h;
490 int i, size = [layers count];
494 for(i = 0; i < size; i++){
495 l = [layers objectAtIndex:i];
496 al = [_animationLayerFactory createNewAnimationLayer:[l duration] name:[l printName] type:[l getType]];
498 [_timeLineController addLayer:al];
506 - (IBAction)openProjectSaveDialog:(id)sender
508 NSSavePanel* sp = [NSSavePanel savePanel];
510 [sp setRequiredFileType:@"elis"];
511 [sp beginSheetForDirectory:nil
513 modalForWindow:_mainWindow
515 didEndSelector:@selector(saveProjectToFile:returnCode:contextInfo:)
519 - (IBAction)openProjectLoadDialog:(id)sender
521 NSOpenPanel* op = [NSOpenPanel openPanel];
524 st = [op runModalForTypes:[NSArray arrayWithObject:@"elis"]];
527 [self loadProjectFromFile:[op filename]];
530 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
532 if([menuItem action] == @selector(rewriteProject:))
533 return savePath != nil;
535 // if([menuItem action] == @selector(removeAllKeyFrame:))
536 // return [_tableController canRemoveAllKeyframe];
538 if([menuItem action] == @selector(removeEffect:))
539 return [_tableController canRemoveEffect];
541 if([menuItem action] == @selector(deleteSelectLayer:))
542 return [_timeLineController canDeleteLayer];
544 if([menuItem action] == @selector(undo:))
545 return [_undoManager canUndo];
547 if([menuItem action] == @selector(redo:))
548 return [_undoManager canRedo];
550 if([menuItem action] == @selector(changeMovieSpeed:))
551 return [self canChangeMovieSpeed];
553 if([menuItem action] == @selector(cutLayerAtCurrentTime:))
554 return [_timeLineController canDeleteLayer];
556 if([menuItem action] == @selector(effectMenuPushed:))
557 return [self canAddEffect];
564 return ![[[[[_timeLineController getSelectLayer] valueForKey:@"ElisLayer"] media] type] isEqualToString:@"sound"];
567 - (IBAction)rewriteProject:(id)sender
569 NSMutableData* data = [NSMutableData data];
570 NSKeyedArchiver* encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
572 [encoder encodeObject:layers forKey:@"layers"];
573 [encoder encodeFloat:ProjectMovieSize.size.width forKey:@"movieWidth"];
574 [encoder encodeFloat:ProjectMovieSize.size.height forKey:@"movieHeight"];
575 [encoder finishEncoding];
577 [data writeToFile:savePath atomically:YES];
580 - (IBAction)changeToSmallWindiw:(id)sender
583 [NSBundle loadNibNamed:@"MainMenuForSmallDisplay" owner:self];
586 - (IBAction)undo:(id)sender
588 ElisLayer* l = [_undoManager popOperation];
590 [_timeLineController updateKeyframeLayer];
591 [_tableController createPropertyTable:l];
592 [_tableController reload];
595 - (IBAction)redo:(id)sender
597 [_undoManager redoOperation];
599 [_timeLineController updateKeyframeLayer];
600 [_tableController reload];
603 - (IBAction)changeMovieSize:(id)sender
605 ElisMovieSizeWindowController* c;
606 c = [[ElisMovieSizeWindowController alloc] init];
608 [c setMainWindow:_mainWindow];
609 [c setMainView:_mainView];
613 - (IBAction)preference:(id)sender
615 ElisPreferenceController*c;
616 c = [[ElisPreferenceController alloc] init];
618 [c setMainWindow:_mainWindow];
622 - (void)textDidChange:(NSNotification*)n
624 CALayer* l = [_timeLineController getSelectLayer];
628 layer = [l valueForKey:@"ElisLayer"];
629 if([[[layer media] type] isEqualToString:@"text"]){
630 NSAttributedString* s = [_textLayerField attributedString];
631 [[layer media] setText:s];
637 - (IBAction)changeMovieSpeed:(id)sender
639 ElisMovieSpeedController* c;
640 c = [[ElisMovieSpeedController alloc] init];
642 [_undoManager pushOperation:[[_timeLineController getSelectLayer] valueForKey:@"ElisLayer"]];
644 [c setMainWindow:_mainWindow];
645 [c setLayer:[[_timeLineController getSelectLayer] valueForKey:@"ElisLayer"]];
651 - (BOOL)canChangeMovieSpeed
654 l = [_timeLineController getSelectLayer];
655 if(l == nil) return NO;
656 if([[[[l valueForKey:@"ElisLayer"] media] type] isEqualToString:@"movie"]) return YES;
660 - (IBAction)gotoNextKeyTime:(id)sender
662 float currentTime = [self getHipTime] * [timeSlider floatValue];
663 NSMutableArray* array = [[NSMutableArray alloc] init];
666 int i, size = [layers count];
668 for(i = 0; i < size; i++){
669 [array addObject:[NSNumber numberWithFloat:0.0]];
670 [array addObject:[NSNumber numberWithFloat:convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time)]];
671 [array addObject:[NSNumber numberWithFloat:convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time) +
672 convertQTTimeToSecond([[layers objectAtIndex:i] mapping].duration)]];
675 [array sortUsingSelector:@selector(compare:)];
676 size = [array count];
678 for(i = 0; i < size; i++)
679 if([[array objectAtIndex:i] floatValue] > currentTime){
680 nextTime = QTMakeTime(([[array objectAtIndex:i] floatValue] +1.0/60.0)* DEFAULT_FPS, DEFAULT_FPS);
681 globalCurrentTime = nextTime;
682 _currentTime = nextTime;
683 [self moveSliderTo:nextTime];
689 - (IBAction)gotoPrevKeyTime:(id)sender
691 float currentTime = [self getHipTime] * [timeSlider floatValue];
692 NSMutableArray* array = [[NSMutableArray alloc] init];
695 int i, size = [layers count];
697 for(i = 0; i < size; i++){
698 [array addObject:[NSNumber numberWithFloat:0.0]];
699 [array addObject:[NSNumber numberWithFloat:convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time)]];
700 [array addObject:[NSNumber numberWithFloat:convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time) +
701 convertQTTimeToSecond([[layers objectAtIndex:i] mapping].duration)]];
704 [array sortUsingSelector:@selector(compare:)];
705 size = [array count];
707 for(i = size-1; i >= 0; i--)
708 if([[array objectAtIndex:i] floatValue] < currentTime){
710 // nextTime = QTMakeTime(([[array objectAtIndex:i] floatValue] -1.0/60)* DEFAULT_FPS, DEFAULT_FPS);
712 // nextTime = QTMakeTime(([[array objectAtIndex:i] floatValue] +1.0/60)* DEFAULT_FPS, DEFAULT_FPS);
713 nextTime = QTMakeTime(([[array objectAtIndex:i] floatValue] +0.0/60)* DEFAULT_FPS, DEFAULT_FPS);
714 globalCurrentTime = nextTime;
715 _currentTime = nextTime;
716 [self moveSliderTo:nextTime];
722 - (IBAction)cutLayerAtCurrentTime:(id)sender
724 ElisLayer* layer = [[_timeLineController getSelectLayer] valueForKey:@"ElisLayer"];
727 new = [layer cutAtTime:globalCurrentTime];
728 [layers addObject:new];
730 [_timeLineController addLayer:[new alayer]];
733 - (IBAction)playStop:(id)sender
735 [[NSGarbageCollector defaultCollector] collectExhaustively];
736 if(playing) [self stopPlay:nil];
737 else [self startPlay:nil];
740 - (NSMutableArray*)writeAudioFiles
742 NSMutableArray* paths = [[NSMutableArray alloc] init];
743 int i, size = [layers count];
744 for(i = 0; i < size; i++){
745 if([[[[layers objectAtIndex:i] media] type] isEqualToString:@"sound"]){
746 ElisAudioWriter* w = [[ElisAudioWriter alloc] initWithLayer:[layers objectAtIndex:i]];
747 NSString* outPath = [NSString stringWithFormat:@"/tmp/%d.wav", i];
748 [w writeToFile:outPath];
749 [paths addObject:outPath];