5 #include "commercials.h"
10 #include "edlsession.h"
13 #include "indexable.h"
14 #include "indexfile.h"
19 #include "mwindowgui.h"
20 #include "pluginset.h"
21 #include "preferences.h"
32 #include <sys/types.h>
38 Commercials(MWindow *mwindow)
39 : Garbage("Commercials")
41 this->mwindow = mwindow;
42 this->scan_status = 0;
56 tracks.remove_all_objects();
68 tracks.remove_all_objects();
75 if( !mwindow->has_commercials() )
77 if( !mdb->is_open() && mdb->openDb() ) {
78 printf("Commercials::openDb failed\n");
88 if( !mwindow->has_commercials() )
90 return mdb->resetDb();
108 return mdb->attachDb(rw);
114 return mdb->detachDb();
118 put_weight(VFrame *frame, int no)
120 int w = frame->get_w(), h = frame->get_h(), rsz = w;
121 uint8_t *tp = frame->get_y();
123 for( int y=h; --y>=0; tp+=rsz ) {
125 for( int x=w; --x>=0; ++bp ) wt += *bp;
127 clip_weights[no] = (double)wt / (w*h);
132 put_frame(VFrame *frame, int no, int group, double offset)
134 int iw = frame->get_w(), ih = frame->get_h();
135 int sw = SWIDTH, sh = SHEIGHT;
136 int slen = sw*sh; uint8_t skey[slen];
137 Scale(frame->get_y(),0,iw,ih,0,0,iw,ih).scale(skey,sw,sh,0,0,sw,sh);
138 int ret = mdb->new_frame(clip_id, skey, no, group, offset);
139 if( ret < 0 ) ret = 0; // ignore forbidden frames
144 put_clip(File *file, int track, double position, double length)
146 if( file->asset->format != FILE_MPEG ) return -1;
147 double framerate; int pid, width, height; char title[BCTEXTLEN];
148 if( file->get_video_info(track, pid, framerate,
149 width, height, title) ) return -1;
150 if( file->set_layer(track) ) return -1;
151 int64_t pos = position * framerate;
152 if( file->set_video_position(pos, 0) ) return 1;
153 time_t ct; time(&ct);
154 int64_t creation_time = (int64_t)ct, system_time;
155 if( file->get_system_time(system_time) ) system_time = 0;
156 int frames = length * framerate;
157 int prefix_size = 2*framerate, length2 = frames/2;
158 if( prefix_size > length2 ) prefix_size = length2;
159 int suffix_size = prefix_size;
161 if( mdb->new_clip_set(title, file->asset->path, position,
162 framerate, frames, prefix_size, suffix_size,
163 creation_time, system_time) ) return 1;
165 clip_id = mdb->clip_id();
167 scan_status = new ScanStatus(this, xS(30), yS(30), 1, 1,
168 cancelled, _("Cutting Ads"));
169 scan_status->update_length(0, frames);
170 scan_status->update_position(0, 0);
171 update_cut_info(track+1, position);
173 clip_weights = mdb->clip_weights();
174 frame_period = 1. / framerate;
175 VFrame frame(width, height, BC_YUV420P);
177 int i = 0, n = 0, result = 0;
178 // first 2 secs of frame data and weights
179 while( i < prefix_size && !result ) {
180 if( (result=file->read_frame(&frame)) != 0 ) break;
181 if( (result=put_frame(&frame, n++, 1, i/framerate)) != 0 ) break;
182 if( (result=put_weight(&frame, i)) != 0 ) break;
183 result = scan_status->update_position(0, ++i);
185 int suffix_start = frames - suffix_size;
186 while( i < suffix_start && !result ) {
187 if( (result=file->read_frame(&frame)) != 0 ) break;
188 if( (result=put_weight(&frame, i)) != 0 ) break;
189 result = scan_status->update_position(0, ++i);
192 // last 2 secs of frame data and weights
193 while( i < frames && !result ) {
194 if( (result=file->read_frame(&frame)) != 0 ) break;
195 if( (result=put_frame(&frame, n++, 2, i/framerate)) != 0 ) break;
196 if( (result=put_weight(&frame, i)) != 0 ) break;
197 result = scan_status->update_position(0, ++i);
201 for( i=0; i<frames; ++i ) wt += clip_weights[i];
202 mdb->clip_average_weight(wt/frames);
214 int trk = tracks.size();
215 while( --trk >= 0 && (clips=tracks.get(trk))->pid != pid );
216 return trk >= 0 ? clips : 0;
220 get_frame(File *file, int pid, double position,
221 uint8_t *tp, int mw, int mh, int ww, int hh)
223 if( file->asset->format != FILE_MPEG ) return -1;
224 int sw = SWIDTH, sh = SHEIGHT;
225 int slen= sw*sh; uint8_t skey[slen];
226 Scale(tp,0,mw,mh,0,0,ww/8,hh/8).scale(skey,sw,sh,0,0,sw,sh);
227 Clips *clips = find_clips(pid);
228 if( !clips ) tracks.append(clips = new Clips(pid));
229 int fid, result = mdb->get_frame_key(skey, fid, clips, position);
234 frame_weight(uint8_t *tdat, int rowsz, int width, int height)
237 for( int y=height; --y>=0; tdat+=rowsz ) {
239 for( int x=width; --x>=0; ++bp ) weight += *bp;
241 return (double)weight / (width*height);
245 skim_frame(Snips *snips, uint8_t *dat, double position)
247 int fid, result = mdb->get_frame_key(dat, fid, snips, position, 1);
248 double weight = frame_weight(dat, SWIDTH, SWIDTH, SHEIGHT);
249 for( Clip *next=0,*clip=snips->first; clip; clip=next ) {
251 result = verify_snip((Snip*)clip, weight, position);
253 if( clip->votes > 2 )
256 else if( result < 0 || --clip->votes < 0 ) {
267 abs_err(Snip *snip, double *ap, int j, int len, double iframerate)
270 int i, k, sz = snip->weights.size(), n = 0;
271 for( i=0; i<sz; ++i ) {
272 k = j + (snip->positions[i] - snip->start) * iframerate + 0.5;
273 if( k < 0 ) continue;
274 if( k >= len ) break;
275 double a = ap[k], b = snip->weights[i];
276 double dv = fabs(a - b);
280 return !n ? MEDIA_WEIGHT_ERRLMT : vv / n;
284 verify_snip(Snip *snip, double weight, double position)
286 int cid = snip->clip_id;
287 if( mdb->clip_id(cid) ) return -1;
288 double iframerate = mdb->clip_framerate();
289 int iframes = mdb->clip_frames();
290 double pos = position - snip->start;
291 int iframe_no = pos * iframerate + 0.5;
292 if( iframe_no >= iframes ) return -1;
293 snip->weights.append(weight);
294 snip->positions.append(position);
295 double *iweights = mdb->clip_weights();
296 double errlmt = MEDIA_WEIGHT_ERRLMT;
297 double err = errlmt, kerr = err;
299 int tmargin = TRANSITION_MARGIN * iframerate + 0.5;
300 for( int j=-2*tmargin; j<=tmargin; ++j ) {
301 err = abs_err(snip, iweights, j, iframes, iframerate);
302 if( err < kerr ) { kerr = err; k = j; }
304 if( kerr >= errlmt ) return 1;
305 if( iframe_no + k >= iframes ) return -1;
311 mute_audio(Clip *clip)
313 Record *record = mwindow->gui->record;
316 mdb->access_clip(clip->clip_id);
318 char clip_title[BCSTRLEN];
319 sprintf(clip_title,"%d",clip_id);
320 record->display_video_text(10, 10, clip_title,
321 BIGFONT, DKGREEN, LTYELLOW, -1, 300., 1.);
325 record->set_mute_gain(0);
326 printf(_("***MUTE***\n"));
334 Record *record = mwindow->gui->record;
337 record->set_mute_gain(1);
338 printf(_("***UNMUTE***\n"));
340 record->undisplay_vframe();
344 int Commercials::skim_weight(int track)
346 if( cancelled ) return 1;
347 int64_t framenum; uint8_t *tdat; int mw, mh;
348 if( scan_file->get_thumbnail(track, framenum, tdat, mw, mh) ) return 1;
349 double framerate; int pid, width, height;
350 if( scan_file->get_video_info(track, pid, framerate,
351 width, height, 0) ) return 1;
352 if( !framerate ) return 1;
353 width /= 8; height /= 8;
354 //write_pbm(tdat,width,height,"/tmp/dat/r%05ld.pbm",framenum);
355 if( height > mh ) height = mh;
356 if( !width || !height ) return 1;
357 weights.append(frame_weight(tdat, mw, width, height));
358 int64_t frame_no = framenum - frame_start;
359 offsets.append(frame_no / framerate);
360 if( scan_status->update_position(1,frame_no) ) return 1;
361 // return < 0, continue. = 0, complete. > 0 error
362 return frame_no < frame_total ? -1 : 0;
365 int Commercials::skim_weight(void *vp, int track)
367 return ((Commercials *)vp)->skim_weight(track);
372 skim_weights(int track, double position, double iframerate, int iframes)
374 double framerate; int pid, width, height; char title[BCTEXTLEN];
375 if( scan_file->get_video_info(track, pid, framerate,
376 width, height, title) ) return 1;
377 if( scan_file->set_layer(track) ) return 1;
378 frame_start = framerate * position;
379 if( scan_file->set_video_position(frame_start, 0) ) return 1;
381 double length = iframes / iframerate;
382 frame_total = length * framerate;
383 scan_status->update_length(1,frame_total);
384 scan_status->update_position(1, 0);
385 weights.remove_all();
386 offsets.remove_all();
387 return scan_file->skim_video(track, this, skim_weight);
391 abs_err(double *ap, int len, double iframerate)
394 int i, k, sz = weights.size(), n = 0;
395 for( i=0; i<sz; ++i ) {
396 k = offsets[i] * iframerate + 0.5;
397 if( k < 0 ) continue;
398 if( k >= len ) break;
399 double a = ap[k], b = weights[i];
400 double dv = fabs(a - b);
404 return !n ? MEDIA_WEIGHT_ERRLMT : vv / n;
409 verify_clip(int clip_id, int track, double position, double &pos)
411 if( mdb->clip_id(clip_id) ) return 1;
412 double iframerate = mdb->clip_framerate();
413 if( iframerate <= 0. ) return 1;
414 int iframes = mdb->clip_frames();
415 int tmargin = TRANSITION_MARGIN * iframerate;
416 int mframes = iframes - 2*tmargin;
417 if( mframes <= 0 ) return 1;
418 double tposition = position + TRANSITION_MARGIN;
419 if( skim_weights(track, tposition, iframerate, mframes) ) return 1;
420 double *iweights = mdb->clip_weights();
421 double err = abs_err(iweights, mframes, iframerate);
422 int k = 0; double kerr = err;
424 for( int j=1; j<n; ++j ) {
425 err = abs_err(iweights+j, mframes, iframerate);
426 if( err < kerr ) { kerr = err; k = j; }
428 //printf("** kerr %f, k=%d\n", kerr, k);
429 if( kerr >= MEDIA_WEIGHT_ERRLMT ) return -1;
430 pos = position + (k - tmargin) / iframerate;
435 write_ads(const char *filename)
437 char index_filename[BCTEXTLEN], source_filename[BCTEXTLEN];
438 IndexFile::get_index_filename(source_filename,
439 mwindow->preferences->index_directory, index_filename,
442 FILE *fp = fopen(index_filename, "wb");
445 xml.tag.set_title("ADS");
447 xml.append_newline();
448 int trks = tracks.size();
449 for( int trk=0; trk<trks; ++trk )
450 tracks.get(trk)->save(xml);
451 xml.tag.set_title("/ADS");
453 xml.append_newline();
454 xml.terminate_string();
455 xml.write_to_file(fp);
462 read_ads(const char *filename)
464 char index_filename[BCTEXTLEN], source_filename[BCTEXTLEN];
465 IndexFile::get_index_filename(source_filename,
466 mwindow->preferences->index_directory, index_filename,
468 tracks.remove_all_objects();
470 if( xml.read_from_file(index_filename, 1) ) return 1;
473 if( xml.read_tag() ) return 1;
474 } while( !xml.tag.title_is("ADS") );
477 if( xml.read_tag() ) return 1;
478 if( xml.tag.title_is("/ADS") ) break;
479 if( xml.tag.title_is("CLIPS") ) {
480 int pid = xml.tag.get_property("PID", (int)0);
481 Clips *clips = new Clips(pid);
482 tracks.append(clips);
483 if( clips->load(xml) ) return 1;
484 if( !xml.tag.title_is("/CLIPS") ) break;
494 for( int i=0; i<tracks.size(); ++i )
496 printf("clip %i:\n", i);
497 for( Clip *clip=tracks.get(i)->first; clip; clip=clip->next )
498 printf(" clip_id %d: votes %d, index %d, groups %d, start %f, end %f\n",
499 clip->clip_id, clip->votes, clip->index, clip->groups,
500 clip->start, clip->end);
505 verify_edit(Track *track, Edit *edit, double start, double end)
507 int64_t clip_start = track->to_units(start,0);
508 int64_t clip_end = track->to_units(end,0);
509 int64_t edit_start = edit->startsource;
510 if( edit_start >= clip_end ) return 1;
511 int64_t edit_end = edit_start + edit->length;
512 if( edit_end <= clip_start ) return 1;
517 cut_edit(Track *track, Edit *edit, int64_t clip_start, int64_t clip_end)
519 int64_t edit_start = edit->startsource;
520 int64_t edit_end = edit_start + edit->length;
521 int64_t cut_start = clip_start < edit_start ? edit_start : clip_start;
522 int64_t cut_end = clip_end >= edit_end ? edit_end : clip_end;
523 int64_t edit_offset = edit->startproject - edit->startsource;
524 // shift from source timeline to project timeline
525 cut_start += edit_offset; cut_end += edit_offset;
526 // cut autos and plugins
527 track->automation->clear(cut_start, cut_end, 0, 1);
528 if( mwindow->edl->session->plugins_follow_edits ) {
529 int sz = track->plugin_set.size();
530 for( int i=0; i<sz; ++i ) {
531 PluginSet *plugin_set = track->plugin_set.values[i];
532 plugin_set->clear(cut_start, cut_end, 0);
536 Edit *next_edit = edit->edits->split_edit(cut_start);
537 int64_t cut_length = cut_end - cut_start;
538 next_edit->length -= cut_length;
539 next_edit->startsource += cut_length;
540 for( Edit *ep=next_edit->next; ep; ep=ep->next )
541 ep->startproject -= cut_length;
546 scan_audio(int vstream, double start, double end)
548 int64_t channel_mask = 0;
549 int astrm = scan_file->get_audio_for_video(vstream, -1, channel_mask);
550 if( astrm < 0 || !channel_mask ) return -1;
551 Tracks *tracks = mwindow->edl->tracks;
552 for(Track *atrk=tracks->first; !cancelled && atrk; atrk=atrk->next) {
553 if( atrk->data_type != TRACK_AUDIO ) continue;
554 if( !atrk->is_armed() ) continue;
555 Edits *edits = atrk->edits; Edit *next = 0;
556 for( Edit *edit=edits->first; !cancelled && edit; edit=next ) {
558 if( ((channel_mask>>edit->channel) & 1) == 0 ) continue;
559 Indexable *indexable = edit->get_source();
560 if( !indexable || !indexable->is_asset ) continue;
561 Asset *asset = (Asset *)indexable;
562 if( !scan_file->asset->equivalent(*asset,0,0,mwindow->edl) ) continue;
563 if( verify_edit(atrk, edit, start, end) ) continue;
564 next = cut_edit(atrk, edit,
565 atrk->to_units(start,0),
566 atrk->to_units(end,0));
577 scan_status = new ScanStatus(this, xS(30), yS(30), 2, 2,
578 cancelled, _("Cutting Ads"));
592 Tracks *tracks = mwindow->edl->tracks;
593 for( Track *vtrk=tracks->first; !cancelled && vtrk; vtrk=vtrk->next) {
594 if( vtrk->data_type != TRACK_VIDEO ) continue;
595 if( !vtrk->is_armed() ) continue;
596 Edits *edits = vtrk->edits; Edit *next = 0;
597 for( Edit *edit=edits->first; !cancelled && edit; edit=next ) {
599 Indexable *indexable = edit->get_source();
600 if( !indexable || !indexable->is_asset ) continue;
601 Asset *asset = (Asset *)indexable;
602 if( update_caption(edit->channel+1,
603 edits->number_of(edit), asset->path) ) break;
604 if( read_ads(asset->path) ) continue;
605 if( scan_asset(asset, vtrk, edit) ) continue;
614 scan_asset(Asset *asset, Track *vtrk, Edit *edit)
617 scan_file = mwindow->video_cache->check_out(asset, mwindow->edl);
618 if( scan_file && scan_file->asset->format == FILE_MPEG ) {
619 result = scan_clips(vtrk, edit);
620 mwindow->video_cache->check_in(asset);
627 scan_clips(Track *vtrk, Edit *edit)
629 int pid = scan_file->get_video_pid(edit->channel);
630 if( pid < 0 ) return 1;
631 Clips *clips = find_clips(pid);
632 if( !clips ) return 1;
633 double last_end = -CLIP_MARGIN;
634 scan_status->update_length(0, clips->total());
635 scan_status->update_position(0, 0);
636 for( Clip *clip=clips->first; !cancelled && clip; clip=clip->next ) {
637 if( verify_edit(vtrk, edit, clip->start, clip->end) ) continue;
638 if( update_status(clips->number_of(clip),
639 clip->start, clip->end) ) break;
640 scan_status->update_position(0, clips->number_of(clip));
641 double start = clip->start;
642 if( verify_clip(clip->clip_id, edit->channel,
643 clip->start, start) ) continue;
644 double length = clip->end - clip->start;
646 if( (length+=start) <= 0 ) continue;
649 double end = start + length;
650 printf(_("cut clip %d in edit @%f %f-%f, clip @%f-%f\n"), clip->clip_id,
651 vtrk->from_units(edit->startsource), vtrk->from_units(edit->startproject),
652 vtrk->from_units(edit->startproject+edit->length),start,end);
653 if( last_end+CLIP_MARGIN > start ) start = last_end;
654 edit = cut_edit(vtrk, edit,
655 vtrk->to_units(start,0),
656 vtrk->to_units(end,0));
657 scan_audio(edit->channel, start, end);
658 mdb->access_clip(clip->clip_id);
666 update_cut_info(int trk, double position)
668 if( cancelled ) return 1;
669 char string[BCTEXTLEN]; EDLSession *session = mwindow->edl->session;
670 Units::totext(string, position, session->time_format, session->sample_rate,
671 session->frame_rate, session->frames_per_foot);
672 char text[BCTEXTLEN]; sprintf(text,_("ad: trk %d@%s "),trk,string);
673 scan_status->update_text(0, text);
678 update_caption(int trk, int edt, const char *path)
680 if( cancelled ) return 1;
681 char text[BCTEXTLEN];
682 snprintf(text,sizeof(text),_("trk%d edt%d asset %s"), trk, edt, path);
683 scan_status->update_text(0, text);
688 update_status(int clip, double start, double end)
690 if( cancelled ) return 1;
691 char text[BCTEXTLEN];
692 snprintf(text,sizeof(text),_("scan: clip%d %f-%f"), clip, start, end);
693 scan_status->update_text(1, text);
699 ScanStatusGUI(ScanStatus *sswindow, int x, int y, int nlines, int nbars)
700 : BC_Window(_("Scanning"), x, y, xS(340),
701 yS(40) + BC_CancelButton::calculate_h() +
702 (BC_Title::calculate_h((BC_WindowBase*) sswindow->
703 commercials->mwindow->gui, _("My")) + yS(5)) * nlines +
704 (BC_ProgressBar::calculate_h() + yS(5)) * nbars, 0, 0, 0)
706 this->sswindow = sswindow;
707 this->nlines = nlines;
709 this->texts = new BC_Title *[nlines];
710 this->bars = new ScanStatusBar *[nbars];
721 create_objects(const char *text)
723 int xs10 = xS(10), xs20 = xS(20);
724 int ys5 = yS(5), ys10 = yS(10);
725 lock_window("ScanStatusGUI::create_objects");
726 int x = xs10, y = ys10;
727 int dy = BC_Title::calculate_h((BC_WindowBase*) sswindow->
728 commercials->mwindow->gui, "My") + ys5;
729 for( int i=0; i<nlines; ++i, y+=dy, text="" )
730 add_tool(texts[i] = new BC_Title(x, y, text));
732 dy = BC_ProgressBar::calculate_h() + ys5;
733 for( int i=0; i<nbars; ++i, y+=dy )
734 add_tool(bars[i] = new ScanStatusBar(x, y, get_w() - xs20, yS(100)));
736 add_tool(new BC_CancelButton(this));
741 ScanStatus(Commercials *commercials, int x, int y,
742 int nlines, int nbars, int &status, const char *text)
746 this->commercials = commercials;
747 gui = new ScanStatusGUI(this, x, y, nlines, nbars);
748 gui->create_objects(text);
762 update_length(int i, int64_t length)
764 if( status ) return 1;
765 gui->bars[i]->update_length(length);
770 update_position(int i, int64_t position)
772 if( status ) return 1;
773 gui->bars[i]->update(position);
778 update_text(int i, const char *text)
780 if( status ) return 1;
781 gui->texts[i]->update(text);
790 if( gui ) gui->set_done(1);
799 gui->create_objects(_("Cutting Ads"));
800 int result = gui->run_window();
801 if( result ) status = 1;
806 void SdbPacketQueue::
807 put_packet(SdbPacket *p)
809 lock("SdbPacketQueue::put_packet");
814 SdbPacket *SdbPacketQueue::
817 lock("SdbPacketQueue::get_packet");
818 SdbPacket *p = first;
829 input_lock = new Condition(0, "SkimDbThread::input_lock");
830 for( int i=32; --i>=0; ) skim_frames.append(new SdbSkimFrame(this));
846 start(Commercials *commercials)
848 commercials->add_user();
849 this->commercials = commercials;
850 if( commercials->openDb() ) return;
851 if( commercials->detachDb() ) return;
861 input_lock->unlock();
865 if( commercials && !commercials->remove_user() )
870 skim(int pid,int64_t framenum,double framerate,
871 uint8_t *idata,int mw,int mh,int iw,int ih)
873 SdbSkimFrame *sf = (SdbSkimFrame *)skim_frames.get_packet();
874 if( !sf ) { printf("SkimDbThread::skim no packet\n"); return 1; }
875 sf->load(pid,framenum,framerate, idata,mw,mh,iw,ih);
881 void SdbPacket::start()
883 thread->put_packet(this);
887 put_packet(SdbPacket *p)
889 active_packets.put_packet(p);
890 input_lock->unlock();
897 input_lock->lock("SkimDbThread::run");
899 SdbPacket *p = active_packets.get_packet();
901 commercials->attachDb();
903 commercials->detachDb();
909 load(int pid,int64_t framenum,double framerate,
910 uint8_t *idata,int mw,int mh,int iw,int ih)
912 int sw=SWIDTH, sh=SHEIGHT;
914 this->framenum = framenum;
915 this->framerate = framerate;
916 Scale(idata,0,mw,mh,0,0,iw/8,ih/8).scale(dat,sw,sh,0,0,sw,sh);
922 double position = framenum / framerate;
923 thread->commercials->skim_frame(thread->snips, dat, position);
924 thread->skim_frames.put_packet(this);
928 void run_that_puppy(const char *fn)
930 FILE *fp = fopen(fn,"r"); double position, length;
931 if( !fp ) { perror("fopen"); return; }
932 MWindow::commercials->resetDb();
933 MWindow *mwindow = MWindow::commercials->mwindow;
934 File *file = mwindow->video_cache->first->file;
936 while( !result && fscanf(fp,"%lf %lf\n",&position, &length) == 2 ) {
937 int result = MWindow::commercials->put_clip(file, 0, position, length);
938 printf(_("cut %f/%f = %d\n"),position,length, result);
940 MWindow::commercials->commitDb();
941 MWindow::commercials->closeDb();