diff options
-rw-r--r-- | libhb/decmpeg2.c | 33 | ||||
-rw-r--r-- | libhb/demuxmpeg.c | 6 | ||||
-rw-r--r-- | libhb/dvd.c | 92 | ||||
-rw-r--r-- | libhb/encxvid.c | 1 | ||||
-rw-r--r-- | libhb/fifo.c | 2 | ||||
-rw-r--r-- | libhb/internal.h | 2 | ||||
-rw-r--r-- | libhb/muxmp4.c | 147 | ||||
-rw-r--r-- | libhb/sync.c | 10 | ||||
-rw-r--r-- | libhb/work.c | 8 | ||||
-rw-r--r-- | macosx/ChapterTitles.h | 28 | ||||
-rw-r--r-- | macosx/ChapterTitles.m | 99 | ||||
-rw-r--r-- | macosx/Controller.h | 7 | ||||
-rw-r--r-- | macosx/Controller.mm | 12 | ||||
-rw-r--r-- | macosx/English.lproj/MainMenu.nib/classes.nib | 1 | ||||
-rw-r--r-- | macosx/English.lproj/MainMenu.nib/info.nib | 7 | ||||
-rw-r--r-- | macosx/English.lproj/MainMenu.nib/keyedobjects.nib | bin | 99793 -> 102083 bytes | |||
-rw-r--r-- | test/Makefile | 6 | ||||
-rw-r--r-- | test/parsecsv.c | 214 | ||||
-rw-r--r-- | test/parsecsv.h | 36 | ||||
-rw-r--r-- | test/test.c | 55 |
20 files changed, 714 insertions, 52 deletions
diff --git a/libhb/decmpeg2.c b/libhb/decmpeg2.c index 214444a05..4fb6ad76b 100644 --- a/libhb/decmpeg2.c +++ b/libhb/decmpeg2.c @@ -21,6 +21,7 @@ struct hb_libmpeg2_s int height; int rate; int got_iframe; + int look_for_break; int64_t last_pts; }; @@ -36,6 +37,7 @@ hb_libmpeg2_t * hb_libmpeg2_init() m->libmpeg2 = mpeg2_init(); m->info = mpeg2_info( m->libmpeg2 ); m->last_pts = -1; + m->look_for_break = 0; return m; } @@ -51,6 +53,7 @@ int hb_libmpeg2_decode( hb_libmpeg2_t * m, hb_buffer_t * buf_es, mpeg2_state_t state; hb_buffer_t * buf; uint8_t * data; + int chap_break = 0; /* Feed libmpeg2 */ if( buf_es->start > -1 ) @@ -86,6 +89,11 @@ int hb_libmpeg2_decode( hb_libmpeg2_t * m, hb_buffer_t * buf_es, } } } + else if( state == STATE_GOP && m->look_for_break == 2) + { + printf("MPEG2: Group of pictures found, searching for I-Frame\n"); + m->look_for_break = 1; + } else if( ( state == STATE_SLICE || state == STATE_END ) && m->info->display_fbuf ) { @@ -93,6 +101,14 @@ int hb_libmpeg2_decode( hb_libmpeg2_t * m, hb_buffer_t * buf_es, PIC_MASK_CODING_TYPE ) == PIC_FLAG_CODING_TYPE_I ) { m->got_iframe = 1; + + // If we are looking for a break, insert the chapter break on an I-Frame + if( m->look_for_break == 1 ) + { + printf("MPEG2: I-Frame Found\n"); + m->look_for_break = 0; + chap_break = 1; + } } if( m->got_iframe ) @@ -100,6 +116,14 @@ int hb_libmpeg2_decode( hb_libmpeg2_t * m, hb_buffer_t * buf_es, buf = hb_buffer_init( m->width * m->height * 3 / 2 ); data = buf->data; + // Was a good break point found? + if( chap_break ) + { + printf("MPEG2: Chapter Break Inserted\n"); + chap_break = 0; + buf->new_chap = 1; + } + memcpy( data, m->info->display_fbuf->buf[0], m->width * m->height ); data += m->width * m->height; @@ -208,6 +232,15 @@ int decmpeg2Work( hb_work_object_t * w, hb_buffer_t ** buf_in, hb_work_private_t * pv = w->private_data; hb_buffer_t * buf, * last = NULL; + // The reader found a chapter break, consume it completely, and remove it from the + // stream. We need to shift it. + if( (*buf_in)->new_chap ) + { + printf("MPEG2: Chapter Break Cell Found, searching for GOP\n"); + pv->libmpeg2->look_for_break = 2; + (*buf_in)->new_chap = 0; + } + hb_libmpeg2_decode( pv->libmpeg2, *buf_in, pv->list ); *buf_out = NULL; diff --git a/libhb/demuxmpeg.c b/libhb/demuxmpeg.c index 721478e80..5aefc3743 100644 --- a/libhb/demuxmpeg.c +++ b/libhb/demuxmpeg.c @@ -110,8 +110,10 @@ int hb_demux_ps( hb_buffer_t * buf_ps, hb_list_t * list_es ) /* Here we hit we ES payload */ buf_es = hb_buffer_init( pes_packet_end - pos ); - buf_es->id = id; - buf_es->start = pts; + buf_es->id = id; + buf_es->start = pts; + buf_es->new_chap = buf_ps->new_chap; // Consume a chapter break, and apply it to the ES. + buf_ps->new_chap = 0; memcpy( buf_es->data, d + pos, pes_packet_end - pos ); hb_list_add( list_es, buf_es ); diff --git a/libhb/dvd.c b/libhb/dvd.c index 9b4ba3fe7..b26c55995 100644 --- a/libhb/dvd.c +++ b/libhb/dvd.c @@ -30,6 +30,7 @@ struct hb_dvd_s int title_block_count; int cell_cur; int cell_next; + int cell_overlap; int block; int pack_len; int next_vobu; @@ -555,7 +556,8 @@ int hb_dvd_start( hb_dvd_t * d, int title, int chapter ) d->block = d->pgc->cell_playback[d->cell_cur].first_sector; d->next_vobu = d->block; d->pack_len = 0; - + d->cell_overlap = 0; + return 1; } @@ -709,6 +711,19 @@ int hb_dvd_read( hb_dvd_t * d, hb_buffer_t * b ) d->cell_cur = d->cell_next; d->next_vobu = d->pgc->cell_playback[d->cell_cur].first_sector; FindNextCell( d ); + d->cell_overlap = 1; + printf("DVD: End of Cell\n"); + } + + // Revert the cell overlap, and check for a chapter break + if( dsi_pack.vobu_sri.prev_vobu == SRI_END_OF_CELL ) + { + printf("DVD: Beginning of Cell\n"); + if( d->cell_overlap ) + { + b->new_chap = hb_dvd_is_break( d ); + d->cell_overlap = 0; + } } } else @@ -736,19 +751,20 @@ int hb_dvd_chapter( hb_dvd_t * d ) { int i; int pgc_id, pgn; + int nr_of_ptts = d->ifo->vts_ptt_srpt->title[d->ttn-1].nr_of_ptts; pgc_t * pgc; - for( i = 0; - i < d->ifo->vts_ptt_srpt->title[d->ttn-1].nr_of_ptts; - i++ ) + for( i = nr_of_ptts - 1; + i >= 0; + i-- ) { /* Get pgc for chapter (i+1) */ pgc_id = d->ifo->vts_ptt_srpt->title[d->ttn-1].ptt[i].pgcn; pgn = d->ifo->vts_ptt_srpt->title[d->ttn-1].ptt[i].pgn; pgc = d->ifo->vts_pgcit->pgci_srp[pgc_id-1].pgc; - if( d->cell_cur >= pgc->program_map[pgn-1] - 1 && - d->cell_cur <= pgc->nr_of_cells - 1 ) + if( d->cell_cur - d->cell_overlap >= pgc->program_map[pgn-1] - 1 && + d->cell_cur - d->cell_overlap <= pgc->nr_of_cells - 1 ) { /* We are in this chapter */ return i + 1; @@ -760,6 +776,70 @@ int hb_dvd_chapter( hb_dvd_t * d ) } /*********************************************************************** + * hb_dvd_is_break + *********************************************************************** + * Returns 1 if the current block is a new chapter start + **********************************************************************/ +int hb_dvd_is_break( hb_dvd_t * d ) +{ + int i, j; + int pgc_id, pgn; + int nr_of_ptts = d->ifo->vts_ptt_srpt->title[d->ttn-1].nr_of_ptts; + pgc_t * pgc; + int cell, chapter_length, cell_end; + + for( i = nr_of_ptts - 1; + i > 0; + i-- ) + { + /* Get pgc for chapter (i+1) */ + pgc_id = d->ifo->vts_ptt_srpt->title[d->ttn-1].ptt[i].pgcn; + pgn = d->ifo->vts_ptt_srpt->title[d->ttn-1].ptt[i].pgn; + pgc = d->ifo->vts_pgcit->pgci_srp[pgc_id-1].pgc; + cell = pgc->program_map[pgn-1] - 1; + + if( cell <= d->cell_start ) + break; + + // This must not match against the start cell. + if( pgc->cell_playback[cell].first_sector == d->block && cell != d->cell_start ) + { + /* Check to see if we merged this chapter into the previous one... */ + /* As a note, merging chapters is probably bad practice for this very reason */ + chapter_length = 0; + + if( i == nr_of_ptts - 1 ) + { + cell_end = d->pgc->nr_of_cells; + } + else + { + cell_end = pgc->program_map[pgn] - 1; + } + + for( j = cell; j < cell_end; j++ ) + { + chapter_length += pgc->cell_playback[j].last_sector + 1 - + pgc->cell_playback[j].first_sector; + } + + if( chapter_length >= 2048 ) + { + printf("DVD: Chapter Break Cell Found\n"); + /* We have a chapter break */ + return 1; + } + else + { + printf("DVD: Cell Found (%d)\n", chapter_length); + } + } + } + + return 0; +} + +/*********************************************************************** * hb_dvd_close *********************************************************************** * Closes and frees everything diff --git a/libhb/encxvid.c b/libhb/encxvid.c index fbaa5a135..7430aca0f 100644 --- a/libhb/encxvid.c +++ b/libhb/encxvid.c @@ -160,6 +160,7 @@ int encxvidWork( hb_work_object_t * w, hb_buffer_t ** buf_in, buf = hb_buffer_init( 3 * job->width * job->height / 2 ); buf->start = in->start; buf->stop = in->stop; + //buf->chap = in->chap; memset( &frame, 0, sizeof( frame ) ); diff --git a/libhb/fifo.c b/libhb/fifo.c index 323406538..fe03c47f9 100644 --- a/libhb/fifo.c +++ b/libhb/fifo.c @@ -129,7 +129,7 @@ hb_buffer_t * hb_fifo_get( hb_fifo_t * f ) b->next = NULL; f->size -= 1; hb_unlock( f->lock ); - + return b; } diff --git a/libhb/internal.h b/libhb/internal.h index efe721e12..f950205d5 100644 --- a/libhb/internal.h +++ b/libhb/internal.h @@ -37,6 +37,7 @@ struct hb_buffer_s int id; int64_t start; int64_t stop; + int new_chap; int key; /* Holds the output PTS from x264, for use by b-frame offsets in muxmp4.c */ @@ -111,6 +112,7 @@ void hb_dvd_stop( hb_dvd_t * ); int hb_dvd_seek( hb_dvd_t *, float ); int hb_dvd_read( hb_dvd_t *, hb_buffer_t * ); int hb_dvd_chapter( hb_dvd_t * ); +int hb_dvd_is_break( hb_dvd_t * d ); void hb_dvd_close( hb_dvd_t ** ); /*********************************************************************** diff --git a/libhb/muxmp4.c b/libhb/muxmp4.c index 7aae352cd..e2de1ce86 100644 --- a/libhb/muxmp4.c +++ b/libhb/muxmp4.c @@ -27,6 +27,10 @@ struct hb_mux_object_s /* Cumulated durations so far, in timescale units (see MP4Mux) */ uint64_t sum_dur; + /* Chapter state information for muxing */ + MP4TrackId chapter_track; + int current_chapter; + uint64_t chapter_duration; }; struct hb_mux_data_s @@ -34,6 +38,91 @@ struct hb_mux_data_s MP4TrackId track; }; +struct hb_text_sample_s +{ + uint8_t sample[1280]; + uint32_t length; + MP4Duration duration; +}; + +/********************************************************************** + * MP4CreateTextSample + ********************************************************************** + * Creates a buffer for a text track sample + *********************************************************************/ +static struct hb_text_sample_s *MP4CreateTextSample( char *textString, uint64_t duration ) +{ + struct hb_text_sample_s *sample = NULL; + int stringLength = strlen(textString); + int x; + + if( stringLength < 1024 ) + { + sample = malloc( sizeof( struct hb_text_sample_s ) ); + + //textLength = (stringLength; // Account for BOM + sample->length = stringLength + 2 + 12; // Account for text length code and other marker + sample->duration = (MP4Duration)duration; + + // 2-byte length marker + sample->sample[0] = (stringLength >> 8) & 0xff; + sample->sample[1] = stringLength & 0xff; + + strncpy( (char *)&(sample->sample[2]), textString, stringLength ); + + x = 2 + stringLength; + + // Modifier Length Marker + sample->sample[x] = 0x00; + sample->sample[x+1] = 0x00; + sample->sample[x+2] = 0x00; + sample->sample[x+3] = 0x0C; + + // Modifier Type Code + sample->sample[x+4] = 'e'; + sample->sample[x+5] = 'n'; + sample->sample[x+6] = 'c'; + sample->sample[x+7] = 'd'; + + // Modifier Value + sample->sample[x+8] = 0x00; + sample->sample[x+9] = 0x00; + sample->sample[x+10] = (256 >> 8) & 0xff; + sample->sample[x+11] = 256 & 0xff; + } + + return sample; +} + +/********************************************************************** + * MP4GenerateChapterSample + ********************************************************************** + * Creates a buffer for a text track sample + *********************************************************************/ +static struct hb_text_sample_s *MP4GenerateChapterSample( hb_mux_object_t * m, uint64_t duration ) +{ + int chapter = m->current_chapter; + hb_chapter_t *chapter_data = hb_list_item( m->job->title->list_chapter, chapter - 1 ); + char tmp_buffer[1024]; + char *string = tmp_buffer; + + tmp_buffer[0] = '\0'; + + if( chapter_data != NULL ) + { + string = chapter_data->title; + } + + if( strlen(string) == 0 || strlen(string) >= 1024 ) + { + snprintf( tmp_buffer, 1023, "Chapter %03i", chapter ); + string = tmp_buffer; + } + + return MP4CreateTextSample( string, duration ); +} + + /********************************************************************** * MP4Init ********************************************************************** @@ -171,36 +260,15 @@ static int MP4Init( hb_mux_object_t * m ) } - if (job->chapter_markers) { - + if (job->chapter_markers) + { /* add a text track for the chapters */ MP4TrackId textTrack; - textTrack = MP4AddChapterTextTrack(m->file, firstAudioTrack); - - /* write the chapter markers for each selected chapter */ - char markerBuf[13]; - hb_chapter_t * chapter; - MP4Duration chapterDuration; - float fOrigDuration, fTimescale; - float fTSDuration; - - for( i = job->chapter_start - 1; i <= job->chapter_end - 1; i++ ) - { - chapter = hb_list_item( title->list_chapter, i ); - - fOrigDuration = chapter->duration; - fTimescale = job->arate; - fTSDuration = (fOrigDuration / 90000) * fTimescale; - chapterDuration = (MP4Duration)fTSDuration; - - sprintf(markerBuf, " Chapter %03i", i + 1); - markerBuf[0] = 0; - markerBuf[1] = 11; // "Chapter xxx" - MP4WriteSample(m->file, textTrack, (u_int8_t*)markerBuf, 13, chapterDuration, 0, true); - - } - + + m->chapter_track = textTrack; + m->chapter_duration = 0; + m->current_chapter = job->chapter_start; } return 0; @@ -214,7 +282,21 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data, uint64_t duration; if( mux_data == job->mux_data ) - { + { + /* Add the sample before the new frame. + It is important that this be calculated prior to the duration + of the new video sample, as we want to sync to right after it. + (This is because of how durations for text tracks work in QT) */ + if( job->chapter_markers && buf->new_chap ) + { + struct hb_text_sample_s *sample = MP4GenerateChapterSample( m, (m->sum_dur - m->chapter_duration) ); + + MP4WriteSample(m->file, m->chapter_track, sample->sample, sample->length, sample->duration, 0, true); + free(sample); + m->current_chapter++; + m->chapter_duration = m->sum_dur; + } + /* Video */ /* Because we use the audio samplerate as the timescale, we have to use potentially variable durations so the video @@ -253,6 +335,15 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data, static int MP4End( hb_mux_object_t * m ) { + /* Write our final chapter marker */ + if( m->job->chapter_markers ) + { + struct hb_text_sample_s *sample = MP4GenerateChapterSample( m, (m->sum_dur - m->chapter_duration) ); + + MP4WriteSample(m->file, m->chapter_track, sample->sample, sample->length, sample->duration, 0, true); + free(sample); + } + #if 0 hb_job_t * job = m->job; char filename[1024]; memset( filename, 0, 1024 ); diff --git a/libhb/sync.c b/libhb/sync.c index fb5d704ac..6ebbf6e0d 100644 --- a/libhb/sync.c +++ b/libhb/sync.c @@ -244,6 +244,7 @@ static int SyncVideo( hb_work_object_t * w ) hb_buffer_t * cur, * next, * sub = NULL; hb_job_t * job = pv->job; int64_t pts_expected; + int chap_break; if( pv->done ) { @@ -310,8 +311,11 @@ static int SyncVideo( hb_work_object_t * w ) } /* Trash current picture */ + /* Also, make sure we don't trash a chapter break */ + chap_break = cur->new_chap; hb_buffer_close( &cur ); pv->cur = cur = hb_fifo_get( job->fifo_raw ); + cur->new_chap |= chap_break; // Don't stomp existing chapter breaks /* Calculate new offset */ pv->pts_offset_old = pv->pts_offset; @@ -358,8 +362,12 @@ static int SyncVideo( hb_work_object_t * w ) { /* The current frame is too old but the next one matches, let's trash */ + /* Also, make sure we don't trash a chapter break */ + chap_break = cur->new_chap; hb_buffer_close( &cur ); pv->cur = cur = hb_fifo_get( job->fifo_raw ); + cur->new_chap |= chap_break; // Make sure we don't stomp the existing one. + continue; } @@ -377,7 +385,7 @@ static int SyncVideo( hb_work_object_t * w ) buf_tmp = cur; pv->cur = cur = hb_fifo_get( job->fifo_raw ); } - + /* Replace those MPEG-2 dates with our dates */ buf_tmp->start = (uint64_t) pv->count_frames * pv->job->vrate_base / 300; diff --git a/libhb/work.c b/libhb/work.c index a4dcb4514..3e5ec4a78 100644 --- a/libhb/work.c +++ b/libhb/work.c @@ -535,6 +535,14 @@ static void work_loop( void * _w ) // w->thread_sleep_interval = MAX(1, (w->thread_sleep_interval - 1)); w->work( w, &buf_in, &buf_out ); + + // Propogate any chapter breaks for the worker + if( buf_in && buf_out && buf_in->new_chap ) + { + printf("WORK: Copying Chapter Break\n"); + buf_out->new_chap = 1; + } + if( buf_in ) { hb_buffer_close( &buf_in ); diff --git a/macosx/ChapterTitles.h b/macosx/ChapterTitles.h new file mode 100644 index 000000000..7f905e0d5 --- /dev/null +++ b/macosx/ChapterTitles.h @@ -0,0 +1,28 @@ +/* ChapterTitles.h $ + + This file is part of the HandBrake source code. + Homepage: <http://handbrake.m0k.org/>. + It may be used under the terms of the GNU General Public License. */ + +#include <Cocoa/Cocoa.h> +#include "hb.h" + +@interface ChapterTitles : NSObject { + hb_title_t *fTitle; +} + +// Trigger a refresh of data +- (void)resetWithTitle:(hb_title_t *)title; + +// Table View Delegates +- (int)numberOfRowsInTableView:(NSTableView *)aTableView; + +- (id)tableView:(NSTableView *)aTableView + objectValueForTableColumn:(NSTableColumn *)aTableColumn + row:(int)rowIndex; + +- (void)tableView:(NSTableView *)aTableView + setObjectValue:(id)anObject + forTableColumn:(NSTableColumn *)aTableColumn + row:(int)rowIndex; +@end diff --git a/macosx/ChapterTitles.m b/macosx/ChapterTitles.m new file mode 100644 index 000000000..26f71913d --- /dev/null +++ b/macosx/ChapterTitles.m @@ -0,0 +1,99 @@ +/* ChapterTitles.m $ + + This file is part of the HandBrake source code. + Homepage: <http://handbrake.m0k.org/>. + It may be used under the terms of the GNU General Public License. */ + +#include "ChapterTitles.h" +#include "hb.h" + +@implementation ChapterTitles +- (id)init +{ + self = [super init]; + if( self != nil ) + { + fTitle = NULL; + } + + return self; +} + +- (void)resetWithTitle:(hb_title_t *)title +{ + int i; + NSString *chapterString; + int count = hb_list_count( title->list_chapter ); + + for( i = 0; i < count; i++ ) + { + hb_chapter_t *chapter = hb_list_item( title->list_chapter, i ); + + if( chapter != NULL && chapter->title[0] == '\0' ) + { + chapterString = [NSString stringWithFormat:@"Chapter %2d",(i+1)]; + + strncpy( chapter->title, [chapterString UTF8String], 1023); + chapter->title[1023] = '\0'; + } + } + + fTitle = title; +} + +- (int)numberOfRowsInTableView:(NSTableView *)aTableView +{ + if( fTitle == NULL ) + { + return 0; + } + else + { + return hb_list_count( fTitle->list_chapter ); + } +} + +- (void)tableView:(NSTableView *)aTableView + setObjectValue:(id)anObject + forTableColumn:(NSTableColumn *)aTableColumn + row:(int)rowIndex +{ + if(aTableColumn != nil && [[aTableColumn identifier] intValue] == 2) + { + hb_chapter_t *chapter = hb_list_item( fTitle->list_chapter, rowIndex ); + + if( chapter != NULL ) + { + strncpy( chapter->title, [anObject UTF8String], 1023); + chapter->title[1023] = '\0'; + } + } +} + +- (id)tableView:(NSTableView *)aTableView + objectValueForTableColumn:(NSTableColumn *)aTableColumn + row:(int)rowIndex +{ + NSString *cellEntry; + + if([[aTableColumn identifier] intValue] == 1) + { + cellEntry = [NSString stringWithFormat:@"%d",rowIndex+1]; + } + else + { + hb_chapter_t *chapter = hb_list_item( fTitle->list_chapter, rowIndex ); + + if( chapter != NULL ) + { + cellEntry = [NSString stringWithUTF8String:chapter->title]; + } + else + { + cellEntry = @"__DATA ERROR__"; + } + } + + return cellEntry; +} +@end diff --git a/macosx/Controller.h b/macosx/Controller.h index a866d58c7..ce9750947 100644 --- a/macosx/Controller.h +++ b/macosx/Controller.h @@ -8,6 +8,7 @@ #include "hb.h" +#include "ChapterTitles.h" #include "ScanController.h" #include "PictureController.h" #include "QueueController.h" @@ -52,7 +53,6 @@ IBOutlet NSTextField * fDstFile1Field; IBOutlet NSTextField * fDstFile2Field; IBOutlet NSButton * fDstBrowseButton; - IBOutlet NSButton * fCreateChapterMarkers; /* Video box */ IBOutlet NSTextField * fVidRateField; @@ -118,6 +118,11 @@ IBOutlet NSPopUpButton * fAudRatePopUp; IBOutlet NSTextField * fAudBitrateField; IBOutlet NSPopUpButton * fAudBitratePopUp; + + /* Chapters box */ + IBOutlet NSButton * fCreateChapterMarkers; + IBOutlet NSTableView * fChapterTable; + ChapterTitles * fChapterTitlesDelegate; /* Bottom */ IBOutlet NSButton * fPictureButton; diff --git a/macosx/Controller.mm b/macosx/Controller.mm index 9e614fa6d..9290cb67a 100644 --- a/macosx/Controller.mm +++ b/macosx/Controller.mm @@ -49,6 +49,8 @@ static int FormatSettings[3][4] = [fPictureController SetHandle: fHandle]; [fQueueController SetHandle: fHandle]; + fChapterTitlesDelegate = [[ChapterTitles alloc] init]; + [fChapterTable setDataSource:fChapterTitlesDelegate]; /* Call UpdateUI every 2/10 sec */ [[NSRunLoop currentRunLoop] addTimer: [NSTimer @@ -738,13 +740,12 @@ static int FormatSettings[3][4] = hb_title_t * title = (hb_title_t *) hb_list_item( list, [fSrcTitlePopUp indexOfSelectedItem] ); hb_job_t * job = title->job; + int i; /* Chapter selection */ job->chapter_start = [fSrcChapterStartPopUp indexOfSelectedItem] + 1; job->chapter_end = [fSrcChapterEndPopUp indexOfSelectedItem] + 1; - - /* Format and codecs */ int format = [fDstFormatPopUp indexOfSelectedItem]; int codecs = [fDstCodecsPopUp indexOfSelectedItem]; @@ -755,7 +756,7 @@ static int FormatSettings[3][4] = mpeg4 and the checkbox being checked */ if ([fDstFormatPopUp indexOfSelectedItem] == 0 && [fCreateChapterMarkers state] == NSOnState) { - job->chapter_markers = 1; + job->chapter_markers = 1; } else { @@ -1136,7 +1137,6 @@ static int FormatSettings[3][4] = //[self EncoderPopUpChanged: NULL]; /* END Get and set the initial pic size for display */ - /* Update subtitle popups */ hb_subtitle_t * subtitle; [fSubPopUp removeAllItems]; @@ -1151,6 +1151,10 @@ static int FormatSettings[3][4] = subtitle->lang] action: NULL keyEquivalent: @""]; } [fSubPopUp selectItemAtIndex: 0]; + + /* Update chapter table */ + [fChapterTitlesDelegate resetWithTitle:title]; + [fChapterTable reloadData]; /* Update audio popups */ [self AddAllAudioTracksToPopUp: fAudLang1PopUp]; diff --git a/macosx/English.lproj/MainMenu.nib/classes.nib b/macosx/English.lproj/MainMenu.nib/classes.nib index 64874fea7..f10f110dc 100644 --- a/macosx/English.lproj/MainMenu.nib/classes.nib +++ b/macosx/English.lproj/MainMenu.nib/classes.nib @@ -52,6 +52,7 @@ fAudTrack1MixPopUp = NSPopUpButton; fAudTrack2MixLabel = NSTextField; fAudTrack2MixPopUp = NSPopUpButton; + fChapterTable = NSTableView; fCreateChapterMarkers = NSButton; fDisplayX264Options = NSTextField; fDstBrowseButton = NSButton; diff --git a/macosx/English.lproj/MainMenu.nib/info.nib b/macosx/English.lproj/MainMenu.nib/info.nib index 54ae4c103..b845f1d90 100644 --- a/macosx/English.lproj/MainMenu.nib/info.nib +++ b/macosx/English.lproj/MainMenu.nib/info.nib @@ -3,7 +3,7 @@ <plist version="1.0"> <dict> <key>IBDocumentLocation</key> - <string>39 277 630 601 0 0 1440 878 </string> + <string>1484 151 630 601 1440 0 1280 1024 </string> <key>IBEditorPositions</key> <dict> <key>1843</key> @@ -17,11 +17,6 @@ <array> <integer>1477</integer> </array> - <key>IBOpenObjects</key> - <array> - <integer>21</integer> - <integer>1438</integer> - </array> <key>IBSystem Version</key> <string>8P2137</string> <key>IBUserGuides</key> diff --git a/macosx/English.lproj/MainMenu.nib/keyedobjects.nib b/macosx/English.lproj/MainMenu.nib/keyedobjects.nib Binary files differindex c52465c1b..560c26596 100644 --- a/macosx/English.lproj/MainMenu.nib/keyedobjects.nib +++ b/macosx/English.lproj/MainMenu.nib/keyedobjects.nib diff --git a/test/Makefile b/test/Makefile index 0f021cc1d..6068484ea 100644 --- a/test/Makefile +++ b/test/Makefile @@ -13,11 +13,13 @@ CXXFLAGS += -I../libhb LIBS2 = ../libhb/libhb.a $(LIBS:%=../contrib/lib/lib%.a) LDFLAGS += $(LIBS2) -../HandBrakeCLI: test.c $(LIBS2) +../HandBrakeCLI: test.c parsecsv.c $(LIBS2) @CMD="$(CC) $(CFLAGS) -o test.o -c test.c"; $$CMD || \ ( echo "Compile line for $@ was:"; echo $$CMD; false ) + @CMD="$(CC) $(CFLAGS) -o parsecsv.o -c parsecsv.c"; $$CMD || \ + ( echo "Compile line for $@ was:"; echo $$CMD; false ) @echo "Link HandBrakeCLI" - @CMD="g++ $(CXXFLAGS) -o ../HandBrakeCLI test.o $(LDFLAGS) -lz -lpthread"; $$CMD || \ + @CMD="g++ $(CXXFLAGS) -o ../HandBrakeCLI test.o parsecsv.o $(LDFLAGS) -lz -lpthread"; $$CMD || \ ( echo "Compile line for $@ was:"; echo $$CMD; false ) @CMD="rm -rf ../plugins ; mkdir ../plugins ; cp ../contrib/lib/libquicktime/* ../plugins"; $$CMD diff --git a/test/parsecsv.c b/test/parsecsv.c new file mode 100644 index 000000000..f0a8a1a18 --- /dev/null +++ b/test/parsecsv.c @@ -0,0 +1,214 @@ +/* $Id: parsecsv.c $ + + This file is part of the HandBrake source code. + Homepage: <http://handbrake.m0k.org/>. + It may be used under the terms of the GNU General Public License. */ + +#include <fcntl.h> +#include "hb.h" +#include "parsecsv.h" + +/* Internal declarations */ +#define is_newline(_x) ( (_x) == 13 || \ + (_x) == 11 || \ + (_x) == 10 ) + +#define is_white(_x) ( (_x) == '\t' || \ + (_x) == ' ' || \ + is_newline(_x) ) + +#define is_sep(_x) ( (_x) == ',' ) + +#define is_esc(_x) ( (_x) == '\\' ) + +#define CSV_CHAR_ERROR 0x8000 +#define CSV_CHAR_EOF 0x4000 +#define CSV_CHAR_ROWSEP 0x2000 +#define CSV_CHAR_COLSEP 0x1000 + +#define CSV_PARSE_NORMAL 0x0000 +#define CSV_PARSE_SEEK 0x0001 +#define CSV_PARSE_ESC 0x0002 + +static uint16_t hb_parse_character( hb_csv_file_t * file ); +static void hb_trim_end( char *text ); + +/* Open a CSV File */ +hb_csv_file_t *hb_open_csv_file( const char *filepath ) +{ + hb_csv_file_t *file = NULL; + FILE * fileref; + + if( filepath == NULL ) + { + return file; + } + + fileref = fopen( filepath, "r" ); + if( fileref == NULL ) + { + return file; + } + + file = malloc( sizeof( hb_csv_file_t ) ); + file->fileref = fileref; + file->eof = 0; + file->parse_state = CSV_PARSE_SEEK; + file->curr_col = 0; + file->curr_row = 0; + return file; +} + +void hb_close_csv_file( hb_csv_file_t *file ) +{ + if( file == NULL ) + { + return; + } + + fclose( file->fileref ); + free( file ); +} + +/* Parse CSV Cells */ +hb_csv_cell_t *hb_read_next_cell( hb_csv_file_t *file ) +{ + hb_csv_cell_t *cell = NULL; + uint16_t c; + int index; + + if( file == NULL ) + { + return cell; + } + + if( file->eof ) + { + return cell; + } + + cell = malloc( sizeof( hb_csv_cell_t ) ); + cell->cell_row = file->curr_row; + cell->cell_col = file->curr_col; + index = 0; + while( CSV_CHAR_EOF != (c = hb_parse_character( file ) ) ) + { + if( c == CSV_CHAR_ROWSEP ) + { + file->curr_row++; + file->curr_col = 0; + break; + } + else if( c == CSV_CHAR_COLSEP ) + { + file->curr_col++; + break; + } + else + { + if( index < 1023 ) + { + cell->cell_text[index] = (char)c; + index++; + } + } + } + + if( c == CSV_CHAR_EOF ) + { + file->eof = 1; + } + + /* Terminate the cell text */ + cell->cell_text[index] = '\0'; + hb_trim_end( cell->cell_text ); + return cell; +} + +void hb_dispose_cell( hb_csv_cell_t *cell ) +{ + if( cell == NULL ) + { + return; + } + + free( cell ); +} + +/* Raw parsing */ +static uint16_t hb_parse_character( hb_csv_file_t * file ) +{ + int byte; + uint16_t c; + int read_result; + int need_char = 1; + + if( file == NULL ) + { + return CSV_CHAR_ERROR; + } + + while( need_char ) + { + byte = fgetc( file->fileref ); + if( feof( file->fileref ) ) + { + return CSV_CHAR_EOF; + } + if( ferror( file->fileref ) ) + { + return CSV_CHAR_ERROR; + } + + if( file->parse_state == CSV_PARSE_SEEK && is_white(byte) ) + { + continue; + } + else if( file->parse_state != CSV_PARSE_ESC && is_esc(byte) ) + { + file->parse_state = CSV_PARSE_ESC; + continue; + } + else if( file->parse_state != CSV_PARSE_ESC && is_sep(byte) ) + { + file->parse_state = CSV_PARSE_SEEK; + need_char = 0; + c = CSV_CHAR_COLSEP; + } + else if( file->parse_state == CSV_PARSE_ESC ) + { + file->parse_state = CSV_PARSE_NORMAL; + need_char = 0; + c = (uint16_t)byte; + } + else if( is_newline(byte) ) + { + file->parse_state = CSV_PARSE_SEEK; + need_char = 0; + c = CSV_CHAR_ROWSEP; + } + else + { + file->parse_state = CSV_PARSE_NORMAL; + need_char = 0; + c = (uint16_t)byte; + } + } + + return c; +} + +static void hb_trim_end( char *text ) +{ + if( text == NULL ) + { + return; + } + + int i = strlen(text) - 1; + + for( i = strlen(text) - 1; i >= 0 && is_white(text[i]) ; i-- ) + { + text[i] = '\0'; + } +}
\ No newline at end of file diff --git a/test/parsecsv.h b/test/parsecsv.h new file mode 100644 index 000000000..6247ddb44 --- /dev/null +++ b/test/parsecsv.h @@ -0,0 +1,36 @@ +/* $Id: parsecsv.h $ + + This file is part of the HandBrake source code. + Homepage: <http://handbrake.m0k.org/>. + It may be used under the terms of the GNU General Public License. */ + +/* + A very simple CSV file parser. + */ + +typedef struct hb_csv_file_s hb_csv_file_t; +typedef struct hb_csv_cell_s hb_csv_cell_t; + +struct hb_csv_file_s +{ + FILE * fileref; + int eof; + int parse_state; + int curr_row; + int curr_col; +}; + +struct hb_csv_cell_s +{ + char cell_text[1024]; + int cell_row; + int cell_col; +}; + +/* Open a CSV File */ +hb_csv_file_t *hb_open_csv_file( const char *filepath ); +void hb_close_csv_file( hb_csv_file_t *file ); + +/* Parse CSV Cells */ +hb_csv_cell_t *hb_read_next_cell( hb_csv_file_t *file ); +void hb_dispose_cell( hb_csv_cell_t *cell );
\ No newline at end of file diff --git a/test/test.c b/test/test.c index 903b330b6..967cd9c11 100644 --- a/test/test.c +++ b/test/test.c @@ -11,6 +11,7 @@ #include <unistd.h> #include "hb.h" +#include "parsecsv.h" /* Options */ static int debug = HB_DEBUG_NONE; @@ -44,6 +45,7 @@ static int pixelratio = 0; static int chapter_start = 0; static int chapter_end = 0; static int chapter_markers = 0; +static char * marker_file = NULL; static int crf = 0; static char *x264opts = NULL; static char *x264opts2 = NULL; @@ -314,6 +316,53 @@ static int HandleEvents( hb_handle_t * h ) if ( chapter_markers ) { job->chapter_markers = chapter_markers; + + if( marker_file != NULL ) + { + hb_csv_file_t * file = hb_open_csv_file( marker_file ); + hb_csv_cell_t * cell; + int row = 0; + int chapter = 0; + + fprintf( stderr, "Reading chapter markers from file %s\n", marker_file ); + + if( file == NULL ) + { + fprintf( stderr, "Cannot open chapter marker file, using defaults\n" ); + } + else + { + /* Parse the cells */ + while( NULL != ( cell = hb_read_next_cell( file ) ) ) + { + /* We have a chapter number */ + if( cell->cell_col == 0 ) + { + row = cell->cell_row; + chapter = atoi( cell->cell_text ); + } + + /* We have a chapter name */ + if( cell->cell_col == 1 && row == cell->cell_row ) + { + /* If we have a valid chapter, copy the string an terminate it */ + if( chapter >= job->chapter_start && chapter <= job->chapter_end ) + { + hb_chapter_t * chapter_s; + + chapter_s = hb_list_item( job->title->list_chapter, chapter - 1); + strncpy(chapter_s->title, cell->cell_text, 1023); + chapter_s->title[1023] = '\0'; + } + } + + + hb_dispose_cell( cell ); + } + + hb_close_csv_file( file ); + } + } } if( crop[0] >= 0 && crop[1] >= 0 && @@ -650,7 +699,7 @@ static int ParseOptions( int argc, char ** argv ) { "title", required_argument, NULL, 't' }, { "chapters", required_argument, NULL, 'c' }, - { "markers", no_argument, NULL, 'm' }, + { "markers", optional_argument, NULL, 'm' }, { "audio", required_argument, NULL, 'a' }, { "mixdown", required_argument, NULL, '6' }, { "subtitle", required_argument, NULL, 's' }, @@ -740,6 +789,10 @@ static int ParseOptions( int argc, char ** argv ) break; } case 'm': + if( optarg != NULL ) + { + marker_file = strdup( optarg ); + } chapter_markers = 1; break; case 'a': |