summaryrefslogtreecommitdiffstats
path: root/macosx/HBPreviewGenerator.m
diff options
context:
space:
mode:
Diffstat (limited to 'macosx/HBPreviewGenerator.m')
-rw-r--r--macosx/HBPreviewGenerator.m423
1 files changed, 423 insertions, 0 deletions
diff --git a/macosx/HBPreviewGenerator.m b/macosx/HBPreviewGenerator.m
new file mode 100644
index 000000000..99ca20f86
--- /dev/null
+++ b/macosx/HBPreviewGenerator.m
@@ -0,0 +1,423 @@
+/* HBPreviewGenerator.m $
+
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr/>.
+ It may be used under the terms of the GNU General Public License. */
+//
+
+#import "HBPreviewGenerator.h"
+#import "Controller.h"
+
+typedef enum EncodeState : NSUInteger {
+ EncodeStateIdle,
+ EncodeStateWorking,
+ EncodeStateCancelled,
+} EncodeState;
+
+@interface HBPreviewGenerator ()
+
+@property (nonatomic, readonly, retain) NSMutableDictionary *picturePreviews;
+@property (nonatomic, readonly) NSUInteger imagesCount;
+@property (nonatomic, readonly) hb_handle_t *handle;
+@property (nonatomic, readonly) hb_title_t *title;
+
+@property (nonatomic) hb_handle_t *privateHandle;
+@property (nonatomic) NSTimer *timer;
+@property (nonatomic) EncodeState encodeState;
+
+@property (nonatomic, retain) NSURL *fileURL;
+
+@end
+
+@implementation HBPreviewGenerator
+
+- (id) initWithHandle: (hb_handle_t *) handle andTitle: (hb_title_t *) title
+{
+ self = [super init];
+ if (self)
+ {
+ _handle = handle;
+ _title = title;
+ _picturePreviews = [[NSMutableDictionary alloc] init];
+ _imagesCount = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
+ }
+ return self;
+}
+
+#pragma mark -
+#pragma mark Preview images
+
+/**
+ * Returns the picture preview at the specified index
+ *
+ * @param index picture index in title.
+ */
+- (NSImage *) imageAtIndex: (NSUInteger) index
+{
+ if (index >= self.imagesCount)
+ return nil;
+
+ // The preview for the specified index may not currently exist, so this method
+ // generates it if necessary.
+ NSImage *theImage = [self.picturePreviews objectForKey:@(index)];
+
+ if (!theImage)
+ {
+ theImage = [HBPreviewGenerator makeImageForPicture:index
+ libhb:self.handle
+ title:self.title
+ deinterlace:self.deinterlace];
+ [self.picturePreviews setObject:theImage forKey:@(index)];
+ }
+
+ return theImage;
+}
+
+/**
+ * Purges all images from the cache. The next call to imageAtIndex: will cause a new
+ * image to be generated.
+ */
+- (void) purgeImageCache
+{
+ [self.picturePreviews removeAllObjects];
+}
+
+/**
+ * This function converts an image created by libhb (specified via pictureIndex) into
+ * an NSImage suitable for the GUI code to use. If removeBorders is YES,
+ * makeImageForPicture crops the image generated by libhb stripping off the gray
+ * border around the content. This is the low-level method that generates the image.
+ * -imageForPicture calls this function whenever it can't find an image in its cache.
+ *
+ * @param picture Index in title.
+ * @param h Handle to hb_handle_t.
+ * @param title Handle to hb_title_t of desired title.
+ */
++ (NSImage *) makeImageForPicture: (NSUInteger) pictureIndex
+ libhb: (hb_handle_t *) handle
+ title: (hb_title_t *) title
+ deinterlace: (BOOL) deinterlace
+{
+ static uint8_t *buffer;
+ static int bufferSize;
+
+ // Make sure we have a big enough buffer to receive the image from libhb
+ int dstWidth = title->job->width;
+ int dstHeight = title->job->height;
+
+ int newSize = dstWidth * dstHeight * 4;
+ if (!buffer || bufferSize < newSize)
+ {
+ bufferSize = newSize;
+ buffer = (uint8_t *) realloc( buffer, bufferSize );
+ }
+
+ // Enable and the disable deinterlace just for preview if deinterlace
+ // or decomb filters are enabled
+ int deinterlaceStatus = title->job->deinterlace;
+ if (deinterlace) title->job->deinterlace = 1;
+
+ hb_get_preview( handle, title->job, (int)pictureIndex, buffer );
+
+ // Reset deinterlace status
+ title->job->deinterlace = deinterlaceStatus;
+
+ // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
+ // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
+ // border around libhb's image.
+
+ // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
+ // Alpha is ignored.
+
+ NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
+ NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:nil
+ pixelsWide:dstWidth
+ pixelsHigh:dstHeight
+ bitsPerSample:8
+ samplesPerPixel:3 // ignore alpha
+ hasAlpha:NO
+ isPlanar:NO
+ colorSpaceName:NSCalibratedRGBColorSpace
+ bitmapFormat:bitmapFormat
+ bytesPerRow:dstWidth * 4
+ bitsPerPixel:32] autorelease];
+
+ UInt32 * src = (UInt32 *)buffer;
+ UInt32 * dst = (UInt32 *)[imgrep bitmapData];
+ int r, c;
+ for (r = 0; r < dstHeight; r++)
+ {
+ for (c = 0; c < dstWidth; c++)
+#if TARGET_RT_LITTLE_ENDIAN
+ *dst++ = Endian32_Swap(*src++);
+#else
+ *dst++ = *src++;
+#endif
+ }
+
+ NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
+ [img addRepresentation:imgrep];
+
+ return img;
+}
+
+#pragma mark -
+#pragma mark Preview movie
+
++ (NSString *) appSupportPath
+{
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSString *appSupportPath = nil;
+
+ NSArray *allPaths = NSSearchPathForDirectoriesInDomains( NSApplicationSupportDirectory,
+ NSUserDomainMask,
+ YES );
+ if ([allPaths count])
+ appSupportPath = [[allPaths objectAtIndex:0] stringByAppendingPathComponent:@"HandBrake"];
+
+ if (![fileManager fileExistsAtPath:appSupportPath])
+ [fileManager createDirectoryAtPath:appSupportPath withIntermediateDirectories:YES attributes:nil error:NULL];
+
+ return appSupportPath;
+}
+
++ (NSURL *) generateFileURLForType:(NSString *) type
+{
+ NSString *previewDirectory = [NSString stringWithFormat:@"%@/Previews/%d", [HBPreviewGenerator appSupportPath], getpid()];
+
+ if (![[NSFileManager defaultManager] fileExistsAtPath:previewDirectory])
+ {
+ if (![[NSFileManager defaultManager] createDirectoryAtPath:previewDirectory
+ withIntermediateDirectories:NO
+ attributes:nil
+ error:nil])
+ return nil;
+ }
+
+ return [[NSURL fileURLWithPath:previewDirectory]
+ URLByAppendingPathComponent:[NSString stringWithFormat:@"preview_temp.%@", type]];
+}
+
+/**
+ * This function start the encode of a movie preview, the delegate will be
+ * called with the updated the progress info and the fileURL.
+ * The called must call HBController prepareJobForPreview before this.
+ *
+ * @param index picture index in title.
+ * @param duration the duration in seconds of the preview movie.
+ */
+- (BOOL) createMovieAsyncWithImageIndex: (NSUInteger) index andDuration: (NSUInteger) duration;
+{
+ /* return if an encoding if already started */
+ if (self.encodeState || index >= self.imagesCount)
+ return NO;
+
+ hb_job_t *job = self.title->job;
+
+ /* Generate the file url and directories. */
+ if (job->mux & HB_MUX_MASK_MP4)
+ {
+ /* we use .m4v for our mp4 files so that ac3 and chapters in mp4 will play properly */
+ self.fileURL = [HBPreviewGenerator generateFileURLForType:@"m4v"];
+ }
+ else if (job->mux & HB_MUX_MASK_MKV)
+ {
+ self.fileURL = [HBPreviewGenerator generateFileURLForType:@"mkv"];
+ }
+
+ /* return if we couldn't get the fileURL */
+ if (!self.fileURL)
+ return NO;
+
+ /* See if there is an existing preview file, if so, delete it */
+ if (![[NSFileManager defaultManager] fileExistsAtPath:[self.fileURL path]])
+ {
+ [[NSFileManager defaultManager] removeItemAtPath:[self.fileURL path] error:NULL];
+ }
+
+ /* We now direct our preview encode to fileURL path */
+ hb_job_set_file(job, [[self.fileURL path] UTF8String]);
+
+ /* We use our advance pref to determine how many previews to scan */
+ job->start_at_preview = (int)index + 1;
+ job->seek_points = (int)self.imagesCount;
+ job->pts_to_stop = duration * 90000LL;
+
+ /* lets go ahead and send it off to libhb
+ * Note: unlike a full encode, we only send 1 pass regardless if the final encode calls for 2 passes.
+ * this should suffice for a fairly accurate short preview and cuts our preview generation time in half.
+ * However we also need to take into account the indepth scan for subtitles.
+ */
+
+ int loggingLevel = [[[NSUserDefaults standardUserDefaults] objectForKey:@"LoggingLevel"] intValue];
+ self.privateHandle = hb_init(loggingLevel, 0);
+
+ /* If scanning we need to do some extra setup of the job. */
+ if (job->indepth_scan == 1)
+ {
+ char *x264opts_tmp;
+ /* When subtitle scan is enabled do a fast pre-scan job
+ * which will determine which subtitles to enable, if any. */
+ job->pass = -1;
+ x264opts_tmp = job->advanced_opts;
+
+ job->advanced_opts = NULL;
+ job->indepth_scan = 1;
+
+ /* Add the pre-scan job */
+ hb_add(self.privateHandle, job);
+ job->advanced_opts = x264opts_tmp;
+ }
+
+ /* Go ahead and perform the actual encoding preview scan */
+ job->indepth_scan = 0;
+ job->pass = 0;
+
+ hb_add(self.privateHandle, job);
+
+ /* we need to clean up the various lists after the job(s) have been set */
+ hb_job_reset(job);
+
+ /* start the actual encode */
+ self.encodeState = EncodeStateWorking;
+ hb_system_sleep_prevent(self.privateHandle);
+
+ [self startHBTimer];
+
+ hb_start(self.privateHandle);
+
+ return YES;
+}
+
+/**
+ * Cancels the encoding process
+ */
+- (void) cancel
+{
+ if (self.privateHandle)
+ {
+ hb_state_t s;
+ hb_get_state2(self.privateHandle, &s);
+
+ if (self.encodeState && (s.state == HB_STATE_WORKING ||
+ s.state == HB_STATE_PAUSED))
+ {
+ self.encodeState = EncodeStateCancelled;
+ hb_stop(self.privateHandle);
+ hb_system_sleep_allow(self.privateHandle);
+ }
+ }
+}
+
+- (void) startHBTimer
+{
+ if (!self.timer)
+ {
+ self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5
+ target:self
+ selector:@selector(updateState)
+ userInfo:nil
+ repeats:YES];
+ }
+}
+
+- (void) stopHBTimer
+{
+ [self.timer invalidate];
+ self.timer = nil;
+}
+
+- (void) updateState
+{
+ hb_state_t s;
+ hb_get_state(self.privateHandle, &s);
+
+ switch( s.state )
+ {
+ case HB_STATE_IDLE:
+ case HB_STATE_SCANNING:
+ case HB_STATE_SCANDONE:
+ break;
+
+ case HB_STATE_WORKING:
+ {
+ NSMutableString *info = [NSMutableString stringWithFormat: @"Encoding preview: %.2f %%", 100.0 * s.param.working.progress];
+
+ if( s.param.working.seconds > -1 )
+ {
+ [info appendFormat:@" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)",
+ s.param.working.rate_cur, s.param.working.rate_avg, s.param.working.hours,
+ s.param.working.minutes, s.param.working.seconds];
+ }
+
+ double progress = 100.0 * s.param.working.progress;
+
+ [self.delegate updateProgress:progress info:info];
+
+ break;
+ }
+
+ case HB_STATE_MUXING:
+ {
+ NSString *info = @"Muxing Preview…";
+ double progress = 100.0;
+
+ [self.delegate updateProgress:progress info:info];
+
+ break;
+ }
+
+ case HB_STATE_PAUSED:
+ break;
+
+ case HB_STATE_WORKDONE:
+ {
+ [self stopHBTimer];
+
+ // Delete all remaining jobs since libhb doesn't do this on its own.
+ hb_job_t * job;
+ while( ( job = hb_job(self.privateHandle, 0) ) )
+ hb_rem( self.handle, job );
+
+ hb_system_sleep_allow(self.privateHandle);
+ hb_stop(self.privateHandle);
+ hb_close(&_privateHandle);
+ self.privateHandle = NULL;
+
+ /* Encode done, call the delegate and close libhb handle */
+ if (self.encodeState != EncodeStateCancelled)
+ {
+ [self.delegate didCreateMovieAtURL:self.fileURL];
+ }
+
+ self.encodeState = EncodeStateIdle;
+
+ break;
+ }
+ }
+}
+
+#pragma mark -
+
+- (void) dealloc
+{
+ [_timer invalidate];
+ [_timer release];
+ _timer = nil;
+
+ if (_privateHandle) {
+ hb_system_sleep_allow(self.privateHandle);
+ hb_stop(_privateHandle);
+ hb_close(&_privateHandle);
+ }
+
+ [_fileURL release];
+ _fileURL = nil;
+ [_picturePreviews release];
+ _picturePreviews = nil;
+
+ [super dealloc];
+}
+
+@end