-
Notifications
You must be signed in to change notification settings - Fork 0
/
SKGenerateThumbnailOperation.m
310 lines (258 loc) · 12.4 KB
/
SKGenerateThumbnailOperation.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
//
// SKGenerateThumbnailOperation.m
// Skreenics
//
// Created by naixn on 13/09/09.
// Copyright 2009 Thibault Martin-Lagardette. All rights reserved.
//
#import "SKGenerateThumbnailOperation.h"
#import "SKPreferencesController.h"
#import "SKProgressIndicator.h"
#import "SKDefines.h"
#import "NSStringAdditions.h"
#define preferenceColorForKey(key) [[NSValueTransformer valueTransformerForName:@"SKRgbToNSColorTransformer"] transformedValue:[userDefaults valueForKey:key]]
@implementation SKGenerateThumbnailOperation
- (id)initWithVideoItem:(SKVideoItem *)aVideoItem
{
if (self = [super init])
{
userDefaults = [NSUserDefaults standardUserDefaults];
videoItem = [aVideoItem retain];
rows = [userDefaults integerForKey:kSKNumberOfRowsPrefKey];
cols = [userDefaults integerForKey:kSKNumberOfColumnsPrefKey];
}
return self;
}
- (void)dealloc
{
[videoItem release];
[super dealloc];
}
#pragma mark Attributed Strings Generation
- (NSAttributedString *)videoResolutionStringFromMovie:(QTMovie *)movie withAttributes:(NSDictionary *)stringAttributes
{
NSSize videoSize;
NSString* videoSizeFormat;
videoSize = [[movie attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
videoSizeFormat = [NSString stringWithFormat:@"\n\tResolution: %.0fx%.0f", videoSize.width, videoSize.height];
return [[[NSAttributedString alloc] initWithString:videoSizeFormat attributes:stringAttributes] autorelease];
}
- (NSAttributedString *)videoFileSizeStringFromMovie:(QTMovie *)movie withAttributes:(NSDictionary *)stringAttributes
{
NSNumber* videoFileSize;
NSString* videoFileSizeFormat;
videoFileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:[movie attributeForKey:QTMovieFileNameAttribute] error:nil] objectForKey:NSFileSize];
videoFileSizeFormat = [NSString stringWithFormat:@"\n\tFilesize: %@", [NSString stringForFileSize:[videoFileSize unsignedLongLongValue]]];
return [[[NSAttributedString alloc] initWithString:videoFileSizeFormat attributes:stringAttributes] autorelease];
}
- (NSAttributedString *)detailsFromMovie:(QTMovie *)movie
{
NSString* movieFilename;
NSMutableDictionary* stringAttributes;
NSMutableAttributedString* resultString;
movieFilename = [[movie attributeForKey:QTMovieFileNameAttribute] lastPathComponent];
// Setup shadow
NSShadow* descriptionShadow = [[[NSShadow alloc] init] autorelease];
[descriptionShadow setShadowOffset:NSMakeSize(1.75, -1.75)];
[descriptionShadow setShadowColor:preferenceColorForKey(kSKImageShadowColorPrefKey)];
[descriptionShadow setShadowBlurRadius:3.0];
// Create default attributes
stringAttributes = [NSMutableDictionary dictionary];
[stringAttributes setObject:[NSFont fontWithName:@"Arial Bold" size:20.0] forKey:NSFontAttributeName];
[stringAttributes setObject:preferenceColorForKey(kSKImageMovieInfoColorPrefKey) forKey:NSForegroundColorAttributeName];
[stringAttributes setObject:descriptionShadow forKey:NSShadowAttributeName];
// Init the result with the filename
resultString = [[NSMutableAttributedString alloc] initWithString:movieFilename attributes:stringAttributes];
// Change attributes for "sub"-info
[stringAttributes removeObjectForKey:NSShadowAttributeName];
[stringAttributes setObject:[NSFont fontWithName:@"Arial Bold" size:15.0] forKey:NSFontAttributeName];
// Add video resolution
[resultString appendAttributedString:[self videoResolutionStringFromMovie:movie withAttributes:stringAttributes]];
// Add file size
[resultString appendAttributedString:[self videoFileSizeStringFromMovie:movie withAttributes:stringAttributes]];
// Return the final string
return [resultString autorelease];
}
- (NSAttributedString *)attributedStringForQTTime:(QTTime)time
{
NSString* timeString;
NSMutableDictionary* stringAttributes;
// Convert the time into a string, and ommit some non-interesting data
timeString = [QTStringFromTime(time) substringWithRange:NSMakeRange(2, 8)];
// Setup string attributes
stringAttributes = [NSMutableDictionary dictionary];
[stringAttributes setObject:[NSFont fontWithName:@"Arial Bold" size:18.0] forKey:NSFontAttributeName];
[stringAttributes setObject:[NSColor colorWithCalibratedWhite:1.0 alpha:0.75] forKey:NSForegroundColorAttributeName];
[stringAttributes setObject:[NSColor colorWithCalibratedWhite:0.0 alpha:0.75] forKey:NSStrokeColorAttributeName];
[stringAttributes setObject:[NSNumber numberWithFloat:-5.0] forKey:NSStrokeWidthAttributeName];
return [[[NSAttributedString alloc] initWithString:timeString attributes:stringAttributes] autorelease];
}
#define PREF_WIDTH [userDefaults floatForKey:kSKImageFileWidthPrefKey]
#define PREF_SPACING [userDefaults floatForKey:kSKSpacingBetweenThumbnailsPrefKey]
#define PREF_SAVEPATH @"/Users/naixn/Desktop/"
#define PREF_MOVIEINFO YES
- (void)main
{
QTMovie* movie;
NSString* movieFilePath;
NSSize movieSize;
NSSize imageSize;
NSRect imageRect;
NSImage* resultImage;
NSImage* currentFrameImage;
NSSize frameAreaSize;
QTTime incrementTime;
QTTime currentTime;
NSString* savePath;
int col;
int row;
// Make sure QTKit is init on this thread
[QTMovie enterQTKitOnThread];
[videoItem setNumberOfSteps:[NSNumber numberWithUnsignedInteger:(5 + cols * rows)]];
// ----------- Step 0: Init movie
if ([self isCancelled])
{
return ;
}
[videoItem setProgressString:@"Opening movie..." incrementProgressValue:NO];
movieFilePath = [videoItem filepath];
NSDictionary* openAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
movieFilePath, QTMovieFileNameAttribute,
[NSNumber numberWithBool:NO], QTMovieOpenAsyncOKAttribute,
nil];
movie = [[QTMovie alloc] initWithAttributes:openAttributes error:nil];
// ----------- Step 1: Check if the movie actually has a movie track
if ([self isCancelled])
{
[movie release];
[QTMovie exitQTKitOnThread];
return ;
}
[videoItem setProgressString:@"Checking if file has a movie track..." incrementProgressValue:YES];
if ([[movie tracksOfMediaType:QTMediaTypeVideo] count] == 0 && [[movie tracksOfMediaType:QTMediaTypeMPEG] count] == 0)
{
[videoItem setError:@"File does not contain a video track"];
[movie release];
[QTMovie exitQTKitOnThread];
return ;
}
// ----------- Step 2: Init some other values
[videoItem setProgressString:@"Preparing..." incrementProgressValue:YES];
// Init some other values
movieSize = [(NSValue *)[movie attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
frameAreaSize.width = (PREF_WIDTH - ((cols + 1) * PREF_SPACING)) / cols;
frameAreaSize.height = (movieSize.height * frameAreaSize.width) / movieSize.width;
imageSize = NSMakeSize(PREF_WIDTH, frameAreaSize.height * rows + (rows + 1) * PREF_SPACING);
// Get the time we will pad around the movie, and set the initial value
incrementTime = [movie duration];
incrementTime.timeValue /= (long long)(cols * rows);
currentTime = incrementTime;
currentTime.timeValue /= 2.0;
// If we need to display some movie details, we need to generate the
// attributed string and add its size to the result image size
NSAttributedString* movieDetails = nil;
NSRect movieDetailsRectOrigin;
if (PREF_MOVIEINFO)
{
movieDetails = [self detailsFromMovie:movie];
movieDetailsRectOrigin = [movieDetails boundingRectWithSize:NSZeroSize options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingDisableScreenFontSubstitution)];
imageSize.height += movieDetailsRectOrigin.size.height + PREF_SPACING;
}
// ----------- Step 3: create original image
if ([self isCancelled])
{
[movie release];
[QTMovie exitQTKitOnThread];
return ;
}
[videoItem setProgressString:@"Creating initial image..." incrementProgressValue:YES];
// Allocate the image in which we will draw, erase everything, and set ready to draw
resultImage = [[NSImage alloc] initWithSize:imageSize];
[resultImage recache];
[resultImage lockFocus];
// Draw background
[preferenceColorForKey(kSKImageBackgroundColorPrefKey) set];
imageRect.origin = NSZeroPoint;
imageRect.size = imageSize;
[NSBezierPath fillRect:imageRect];
[NSBezierPath setDefaultLineWidth:1.5];
// Draw movie info
if (PREF_MOVIEINFO && movieDetails)
{
[movieDetails drawAtPoint:NSMakePoint(PREF_SPACING, imageSize.height - PREF_SPACING - movieDetailsRectOrigin.size.height)];
}
// Setup the shadow
NSShadow* thumbnailShadow = [[NSShadow alloc] init];
[thumbnailShadow setShadowOffset:NSMakeSize(2.0, -2.0)];
[thumbnailShadow setShadowColor:preferenceColorForKey(kSKImageShadowColorPrefKey)];
[thumbnailShadow setShadowBlurRadius:3.0];
for (row = 0; row < rows; row++)
{
for (col = 0; col < cols; col++)
{
// ----------- Step 4: create thumbnail
if ([self isCancelled])
{
[resultImage unlockFocus];
[resultImage release];
[thumbnailShadow release];
[movie release];
[QTMovie exitQTKitOnThread];
return ;
}
[videoItem setProgressString:[NSString stringWithFormat:@"Processing frame %d of %d...", (int)((row * cols) + col) + 1, (int)(rows * cols)]
incrementProgressValue:YES];
// Get current frame image, and compute frame position
currentFrameImage = [movie frameImageAtTime:currentTime];
CGFloat x = (col * frameAreaSize.width) + ((col + 1) * PREF_SPACING);
CGFloat y = ((rows - row - 1) * frameAreaSize.height) + ((rows - row) * PREF_SPACING);
// Draw frame image
[NSGraphicsContext saveGraphicsState];
[thumbnailShadow set];
NSRect drawingRect = NSMakeRect(x, y, frameAreaSize.width, frameAreaSize.height);
NSRect sourceRect = NSMakeRect(0, 0, [currentFrameImage size].width, [currentFrameImage size].height);
[currentFrameImage drawInRect:drawingRect fromRect:sourceRect operation:NSCompositeCopy fraction:1.0];
[NSGraphicsContext restoreGraphicsState];
// Draw border
[[[NSColor blackColor] colorWithAlphaComponent:0.75] set];
[NSBezierPath strokeRect:drawingRect];
// Draw timestamp
[[self attributedStringForQTTime:currentTime] drawAtPoint:NSMakePoint(x + 5.0, y + 5.0)];
// Get further in the video
currentTime = QTTimeIncrement(currentTime, incrementTime);
}
}
// We are done drawing on the image
[resultImage unlockFocus];
// ----------- Step 5: Write result to HD
if ([self isCancelled] == NO)
{
NSString* imageExtension = [SKPreferencesController imageFileExtension];
NSBitmapImageFileType imageFileType = [SKPreferencesController imageFileType];
[videoItem setProgressString:@"Writing image..." incrementProgressValue:YES];
// Write the result on the hard drive
if ([userDefaults boolForKey:kSKPreferMovieFileFolderPrefKey])
{
savePath = [[movieFilePath stringByDeletingPathExtension] stringByAppendingPathExtension:imageExtension];
}
else
{
NSString* outputFolder = [[userDefaults objectForKey:kSKOuputFolderPrefKey] stringByExpandingTildeInPath];
NSString* outputFileName = [[[videoItem filename] stringByDeletingPathExtension] stringByAppendingPathExtension:imageExtension];
savePath = [outputFolder stringByAppendingPathComponent:outputFileName];
}
NSBitmapImageRep* repr = [NSBitmapImageRep imageRepWithData:[resultImage TIFFRepresentation]];
[[repr representationUsingType:imageFileType properties:nil] writeToFile:savePath atomically:YES];
}
// Release all our manually allocated data
[thumbnailShadow release];
[resultImage release];
[movie release];
[QTMovie exitQTKitOnThread];
// ----------- Done
if ([self isCancelled] == NO)
{
[videoItem setProgressString:@"Done!" incrementProgressValue:YES];
}
}
@end