3 * Copyright (C) 2016-2020 William Morrow
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published
7 * by the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 #include "arraylist.h"
24 #include "commercials.h"
29 #include "edlsession.h"
32 #include "indexable.h"
33 #include "indexfile.h"
38 #include "mwindowgui.h"
39 #include "pluginset.h"
40 #include "preferences.h"
51 #include <sys/types.h>
57 Commercials(MWindow *mwindow)
58 : Garbage("Commercials")
60 this->mwindow = mwindow;
61 this->scan_status = 0;
75 tracks.remove_all_objects();
87 tracks.remove_all_objects();
94 if( !mwindow->has_commercials() )
96 if( !mdb->is_open() && mdb->openDb() ) {
97 printf("Commercials::openDb failed\n");
107 if( !mwindow->has_commercials() )
109 return mdb->resetDb();
127 return mdb->attachDb(rw);
133 return mdb->detachDb();
137 put_weight(VFrame *frame, int no)
139 int w = frame->get_w(), h = frame->get_h(), rsz = w;
140 uint8_t *tp = frame->get_y();
142 for( int y=h; --y>=0; tp+=rsz ) {
144 for( int x=w; --x>=0; ++bp ) wt += *bp;
146 clip_weights[no] = (double)wt / (w*h);
151 put_frame(VFrame *frame, int no, int group, double offset)
153 int iw = frame->get_w(), ih = frame->get_h();
154 int sw = SWIDTH, sh = SHEIGHT;
155 int slen = sw*sh; uint8_t skey[slen];
156 Scale(frame->get_y(),0,iw,ih,0,0,iw,ih).scale(skey,sw,sh,0,0,sw,sh);
157 int ret = mdb->new_frame(clip_id, skey, no, group, offset);
158 if( ret < 0 ) ret = 0; // ignore forbidden frames
163 put_clip(File *file, int track, double position, double length)
165 if( file->asset->format != FILE_MPEG ) return -1;
166 double framerate; int pid, width, height; char title[BCTEXTLEN];
167 if( file->get_video_info(track, pid, framerate,
168 width, height, title) ) return -1;
169 if( file->set_layer(track) ) return -1;
170 int64_t pos = position * framerate;
171 if( file->set_video_position(pos, 0) ) return 1;
172 time_t ct; time(&ct);
173 int64_t creation_time = (int64_t)ct, system_time;
174 if( file->get_system_time(system_time) ) system_time = 0;
175 int frames = length * framerate;
176 int prefix_size = 2*framerate, length2 = frames/2;
177 if( prefix_size > length2 ) prefix_size = length2;
178 int suffix_size = prefix_size;
180 if( mdb->new_clip_set(title, file->asset->path, position,
181 framerate, frames, prefix_size, suffix_size,
182 creation_time, system_time) ) return 1;
184 clip_id = mdb->clip_id();
186 scan_status = new ScanStatus(this, xS(30), yS(30), 1, 1,
187 cancelled, _("Cutting Ads"));
188 scan_status->update_length(0, frames);
189 scan_status->update_position(0, 0);
190 update_cut_info(track+1, position);
192 clip_weights = mdb->clip_weights();
193 frame_period = 1. / framerate;
194 VFrame frame(width, height, BC_YUV420P);
196 int i = 0, n = 0, result = 0;
197 // first 2 secs of frame data and weights
198 while( i < prefix_size && !result ) {
199 if( (result=file->read_frame(&frame)) != 0 ) break;
200 if( (result=put_frame(&frame, n++, 1, i/framerate)) != 0 ) break;
201 if( (result=put_weight(&frame, i)) != 0 ) break;
202 result = scan_status->update_position(0, ++i);
204 int suffix_start = frames - suffix_size;
205 while( i < suffix_start && !result ) {
206 if( (result=file->read_frame(&frame)) != 0 ) break;
207 if( (result=put_weight(&frame, i)) != 0 ) break;
208 result = scan_status->update_position(0, ++i);
211 // last 2 secs of frame data and weights
212 while( i < frames && !result ) {
213 if( (result=file->read_frame(&frame)) != 0 ) break;
214 if( (result=put_frame(&frame, n++, 2, i/framerate)) != 0 ) break;
215 if( (result=put_weight(&frame, i)) != 0 ) break;
216 result = scan_status->update_position(0, ++i);
220 for( i=0; i<frames; ++i ) wt += clip_weights[i];
221 mdb->clip_average_weight(wt/frames);
233 int trk = tracks.size();
234 while( --trk >= 0 && (clips=tracks.get(trk))->pid != pid );
235 return trk >= 0 ? clips : 0;
239 get_frame(File *file, int pid, double position,
240 uint8_t *tp, int mw, int mh, int ww, int hh)
242 if( file->asset->format != FILE_MPEG ) return -1;
243 int sw = SWIDTH, sh = SHEIGHT;
244 int slen= sw*sh; uint8_t skey[slen];
245 Scale(tp,0,mw,mh,0,0,ww/8,hh/8).scale(skey,sw,sh,0,0,sw,sh);
246 Clips *clips = find_clips(pid);
247 if( !clips ) tracks.append(clips = new Clips(pid));
248 int fid, result = mdb->get_frame_key(skey, fid, clips, position);
253 frame_weight(uint8_t *tdat, int rowsz, int width, int height)
256 for( int y=height; --y>=0; tdat+=rowsz ) {
258 for( int x=width; --x>=0; ++bp ) weight += *bp;
260 return (double)weight / (width*height);
264 skim_frame(Snips *snips, uint8_t *dat, double position)
266 int fid, result = mdb->get_frame_key(dat, fid, snips, position, 1);
267 double weight = frame_weight(dat, SWIDTH, SWIDTH, SHEIGHT);
268 for( Clip *next=0,*clip=snips->first; clip; clip=next ) {
270 result = verify_snip((Snip*)clip, weight, position);
272 if( clip->votes > 2 )
275 else if( result < 0 || --clip->votes < 0 ) {
286 abs_err(Snip *snip, double *ap, int j, int len, double iframerate)
289 int i, k, sz = snip->weights.size(), n = 0;
290 for( i=0; i<sz; ++i ) {
291 k = j + (snip->positions[i] - snip->start) * iframerate + 0.5;
292 if( k < 0 ) continue;
293 if( k >= len ) break;
294 double a = ap[k], b = snip->weights[i];
295 double dv = fabs(a - b);
299 return !n ? MEDIA_WEIGHT_ERRLMT : vv / n;
303 verify_snip(Snip *snip, double weight, double position)
305 int cid = snip->clip_id;
306 if( mdb->clip_id(cid) ) return -1;
307 double iframerate = mdb->clip_framerate();
308 int iframes = mdb->clip_frames();
309 double pos = position - snip->start;
310 int iframe_no = pos * iframerate + 0.5;
311 if( iframe_no >= iframes ) return -1;
312 snip->weights.append(weight);
313 snip->positions.append(position);
314 double *iweights = mdb->clip_weights();
315 double errlmt = MEDIA_WEIGHT_ERRLMT;
316 double err = errlmt, kerr = err;
318 int tmargin = TRANSITION_MARGIN * iframerate + 0.5;
319 for( int j=-2*tmargin; j<=tmargin; ++j ) {
320 err = abs_err(snip, iweights, j, iframes, iframerate);
321 if( err < kerr ) { kerr = err; k = j; }
323 if( kerr >= errlmt ) return 1;
324 if( iframe_no + k >= iframes ) return -1;
330 mute_audio(Clip *clip)
332 Record *record = mwindow->gui->record;
335 mdb->access_clip(clip->clip_id);
337 char clip_title[BCSTRLEN];
338 sprintf(clip_title,"%d",clip_id);
339 record->display_video_text(10, 10, clip_title,
340 BIGFONT, DKGREEN, LTYELLOW, -1, 300., 1.);
344 record->set_mute_gain(0);
345 printf(_("***MUTE***\n"));
353 Record *record = mwindow->gui->record;
356 record->set_mute_gain(1);
357 printf(_("***UNMUTE***\n"));
359 record->undisplay_vframe();
363 int Commercials::skim_weight(int track)
365 if( cancelled ) return 1;
366 int64_t framenum; uint8_t *tdat; int mw, mh;
367 if( scan_file->get_thumbnail(track, framenum, tdat, mw, mh) ) return 1;
368 double framerate; int pid, width, height;
369 if( scan_file->get_video_info(track, pid, framerate,
370 width, height, 0) ) return 1;
371 if( !framerate ) return 1;
372 width /= 8; height /= 8;
373 //write_pbm(tdat,width,height,"/tmp/dat/r%05ld.pbm",framenum);
374 if( height > mh ) height = mh;
375 if( !width || !height ) return 1;
376 weights.append(frame_weight(tdat, mw, width, height));
377 int64_t frame_no = framenum - frame_start;
378 offsets.append(frame_no / framerate);
379 if( scan_status->update_position(1,frame_no) ) return 1;
380 // return < 0, continue. = 0, complete. > 0 error
381 return frame_no < frame_total ? -1 : 0;
384 int Commercials::skim_weight(void *vp, int track)
386 return ((Commercials *)vp)->skim_weight(track);
391 skim_weights(int track, double position, double iframerate, int iframes)
393 double framerate; int pid, width, height; char title[BCTEXTLEN];
394 if( scan_file->get_video_info(track, pid, framerate,
395 width, height, title) ) return 1;
396 if( scan_file->set_layer(track) ) return 1;
397 frame_start = framerate * position;
398 if( scan_file->set_video_position(frame_start, 0) ) return 1;
400 double length = iframes / iframerate;
401 frame_total = length * framerate;
402 scan_status->update_length(1,frame_total);
403 scan_status->update_position(1, 0);
404 weights.remove_all();
405 offsets.remove_all();
406 return scan_file->skim_video(track, this, skim_weight);
410 abs_err(double *ap, int len, double iframerate)
413 int i, k, sz = weights.size(), n = 0;
414 for( i=0; i<sz; ++i ) {
415 k = offsets[i] * iframerate + 0.5;
416 if( k < 0 ) continue;
417 if( k >= len ) break;
418 double a = ap[k], b = weights[i];
419 double dv = fabs(a - b);
423 return !n ? MEDIA_WEIGHT_ERRLMT : vv / n;
428 verify_clip(int clip_id, int track, double position, double &pos)
430 if( mdb->clip_id(clip_id) ) return 1;
431 double iframerate = mdb->clip_framerate();
432 if( iframerate <= 0. ) return 1;
433 int iframes = mdb->clip_frames();
434 int tmargin = TRANSITION_MARGIN * iframerate;
435 int mframes = iframes - 2*tmargin;
436 if( mframes <= 0 ) return 1;
437 double tposition = position + TRANSITION_MARGIN;
438 if( skim_weights(track, tposition, iframerate, mframes) ) return 1;
439 double *iweights = mdb->clip_weights();
440 double err = abs_err(iweights, mframes, iframerate);
441 int k = 0; double kerr = err;
443 for( int j=1; j<n; ++j ) {
444 err = abs_err(iweights+j, mframes, iframerate);
445 if( err < kerr ) { kerr = err; k = j; }
447 //printf("** kerr %f, k=%d\n", kerr, k);
448 if( kerr >= MEDIA_WEIGHT_ERRLMT ) return -1;
449 pos = position + (k - tmargin) / iframerate;
454 write_ads(const char *filename)
456 char index_filename[BCTEXTLEN], source_filename[BCTEXTLEN];
457 IndexFile::get_index_filename(source_filename,
458 mwindow->preferences->index_directory, index_filename,
461 FILE *fp = fopen(index_filename, "wb");
464 xml.tag.set_title("ADS");
466 xml.append_newline();
467 int trks = tracks.size();
468 for( int trk=0; trk<trks; ++trk )
469 tracks.get(trk)->save(xml);
470 xml.tag.set_title("/ADS");
472 xml.append_newline();
473 xml.terminate_string();
474 xml.write_to_file(fp);
481 read_ads(const char *filename)
483 char index_filename[BCTEXTLEN], source_filename[BCTEXTLEN];
484 IndexFile::get_index_filename(source_filename,
485 mwindow->preferences->index_directory, index_filename,
487 tracks.remove_all_objects();
489 if( xml.read_from_file(index_filename, 1) ) return 1;
492 if( xml.read_tag() ) return 1;
493 } while( !xml.tag.title_is("ADS") );
496 if( xml.read_tag() ) return 1;
497 if( xml.tag.title_is("/ADS") ) break;
498 if( xml.tag.title_is("CLIPS") ) {
499 int pid = xml.tag.get_property("PID", (int)0);
500 Clips *clips = new Clips(pid);
501 tracks.append(clips);
502 if( clips->load(xml) ) return 1;
503 if( !xml.tag.title_is("/CLIPS") ) break;
513 for( int i=0; i<tracks.size(); ++i )
515 printf("clip %i:\n", i);
516 for( Clip *clip=tracks.get(i)->first; clip; clip=clip->next )
517 printf(" clip_id %d: votes %d, index %d, groups %d, start %f, end %f\n",
518 clip->clip_id, clip->votes, clip->index, clip->groups,
519 clip->start, clip->end);
524 verify_edit(Track *track, Edit *edit, double start, double end)
526 int64_t clip_start = track->to_units(start,0);
527 int64_t clip_end = track->to_units(end,0);
528 int64_t edit_start = edit->startsource;
529 if( edit_start >= clip_end ) return 1;
530 int64_t edit_end = edit_start + edit->length;
531 if( edit_end <= clip_start ) return 1;
536 cut_edit(Track *track, Edit *edit, int64_t clip_start, int64_t clip_end)
538 int64_t edit_start = edit->startsource;
539 int64_t edit_end = edit_start + edit->length;
540 int64_t cut_start = clip_start < edit_start ? edit_start : clip_start;
541 int64_t cut_end = clip_end >= edit_end ? edit_end : clip_end;
542 int64_t edit_offset = edit->startproject - edit->startsource;
543 // shift from source timeline to project timeline
544 cut_start += edit_offset; cut_end += edit_offset;
545 // cut autos and plugins
546 track->automation->clear(cut_start, cut_end, 0, 1);
547 if( mwindow->edl->session->plugins_follow_edits ) {
548 int sz = track->plugin_set.size();
549 for( int i=0; i<sz; ++i ) {
550 PluginSet *plugin_set = track->plugin_set.values[i];
551 plugin_set->clear(cut_start, cut_end, 0);
555 Edit *next_edit = edit->edits->split_edit(cut_start);
556 int64_t cut_length = cut_end - cut_start;
557 next_edit->length -= cut_length;
558 next_edit->startsource += cut_length;
559 for( Edit *ep=next_edit->next; ep; ep=ep->next )
560 ep->startproject -= cut_length;
565 scan_audio(int vstream, double start, double end)
567 int64_t channel_mask = 0;
568 int astrm = scan_file->get_audio_for_video(vstream, -1, channel_mask);
569 if( astrm < 0 || !channel_mask ) return -1;
570 Tracks *tracks = mwindow->edl->tracks;
571 for(Track *atrk=tracks->first; !cancelled && atrk; atrk=atrk->next) {
572 if( atrk->data_type != TRACK_AUDIO ) continue;
573 if( !atrk->is_armed() ) continue;
574 Edits *edits = atrk->edits; Edit *next = 0;
575 for( Edit *edit=edits->first; !cancelled && edit; edit=next ) {
577 if( ((channel_mask>>edit->channel) & 1) == 0 ) continue;
578 Indexable *indexable = edit->get_source();
579 if( !indexable || !indexable->is_asset ) continue;
580 Asset *asset = (Asset *)indexable;
581 if( !scan_file->asset->equivalent(*asset,0,0,mwindow->edl) ) continue;
582 if( verify_edit(atrk, edit, start, end) ) continue;
583 next = cut_edit(atrk, edit,
584 atrk->to_units(start,0),
585 atrk->to_units(end,0));
596 scan_status = new ScanStatus(this, xS(30), yS(30), 2, 2,
597 cancelled, _("Cutting Ads"));
611 Tracks *tracks = mwindow->edl->tracks;
612 for( Track *vtrk=tracks->first; !cancelled && vtrk; vtrk=vtrk->next) {
613 if( vtrk->data_type != TRACK_VIDEO ) continue;
614 if( !vtrk->is_armed() ) continue;
615 Edits *edits = vtrk->edits; Edit *next = 0;
616 for( Edit *edit=edits->first; !cancelled && edit; edit=next ) {
618 Indexable *indexable = edit->get_source();
619 if( !indexable || !indexable->is_asset ) continue;
620 Asset *asset = (Asset *)indexable;
621 if( update_caption(edit->channel+1,
622 edits->number_of(edit), asset->path) ) break;
623 if( read_ads(asset->path) ) continue;
624 if( scan_asset(asset, vtrk, edit) ) continue;
633 scan_asset(Asset *asset, Track *vtrk, Edit *edit)
636 scan_file = mwindow->video_cache->check_out(asset, mwindow->edl);
637 if( scan_file && scan_file->asset->format == FILE_MPEG ) {
638 result = scan_clips(vtrk, edit);
639 mwindow->video_cache->check_in(asset);
646 scan_clips(Track *vtrk, Edit *edit)
648 int pid = scan_file->get_video_pid(edit->channel);
649 if( pid < 0 ) return 1;
650 Clips *clips = find_clips(pid);
651 if( !clips ) return 1;
652 double last_end = -CLIP_MARGIN;
653 scan_status->update_length(0, clips->total());
654 scan_status->update_position(0, 0);
655 for( Clip *clip=clips->first; !cancelled && clip; clip=clip->next ) {
656 if( verify_edit(vtrk, edit, clip->start, clip->end) ) continue;
657 if( update_status(clips->number_of(clip),
658 clip->start, clip->end) ) break;
659 scan_status->update_position(0, clips->number_of(clip));
660 double start = clip->start;
661 if( verify_clip(clip->clip_id, edit->channel,
662 clip->start, start) ) continue;
663 double length = clip->end - clip->start;
665 if( (length+=start) <= 0 ) continue;
668 double end = start + length;
669 printf(_("cut clip %d in edit @%f %f-%f, clip @%f-%f\n"), clip->clip_id,
670 vtrk->from_units(edit->startsource), vtrk->from_units(edit->startproject),
671 vtrk->from_units(edit->startproject+edit->length),start,end);
672 if( last_end+CLIP_MARGIN > start ) start = last_end;
673 edit = cut_edit(vtrk, edit,
674 vtrk->to_units(start,0),
675 vtrk->to_units(end,0));
676 scan_audio(edit->channel, start, end);
677 mdb->access_clip(clip->clip_id);
685 update_cut_info(int trk, double position)
687 if( cancelled ) return 1;
688 char string[BCTEXTLEN]; EDLSession *session = mwindow->edl->session;
689 Units::totext(string, position, session->time_format, session->sample_rate,
690 session->frame_rate, session->frames_per_foot);
691 char text[BCTEXTLEN]; sprintf(text,_("ad: trk %d@%s "),trk,string);
692 scan_status->update_text(0, text);
697 update_caption(int trk, int edt, const char *path)
699 if( cancelled ) return 1;
700 char text[BCTEXTLEN];
701 snprintf(text,sizeof(text),_("trk%d edt%d asset %s"), trk, edt, path);
702 scan_status->update_text(0, text);
707 update_status(int clip, double start, double end)
709 if( cancelled ) return 1;
710 char text[BCTEXTLEN];
711 snprintf(text,sizeof(text),_("scan: clip%d %f-%f"), clip, start, end);
712 scan_status->update_text(1, text);
718 ScanStatusGUI(ScanStatus *sswindow, int x, int y, int nlines, int nbars)
719 : BC_Window(_("Scanning"), x, y, xS(340),
720 yS(40) + BC_CancelButton::calculate_h() +
721 (BC_Title::calculate_h((BC_WindowBase*) sswindow->
722 commercials->mwindow->gui, _("My")) + yS(5)) * nlines +
723 (BC_ProgressBar::calculate_h() + yS(5)) * nbars, 0, 0, 0)
725 this->sswindow = sswindow;
726 this->nlines = nlines;
728 this->texts = new BC_Title *[nlines];
729 this->bars = new ScanStatusBar *[nbars];
740 create_objects(const char *text)
742 int xs10 = xS(10), xs20 = xS(20);
743 int ys5 = yS(5), ys10 = yS(10);
744 lock_window("ScanStatusGUI::create_objects");
745 int x = xs10, y = ys10;
746 int dy = BC_Title::calculate_h((BC_WindowBase*) sswindow->
747 commercials->mwindow->gui, "My") + ys5;
748 for( int i=0; i<nlines; ++i, y+=dy, text="" )
749 add_tool(texts[i] = new BC_Title(x, y, text));
751 dy = BC_ProgressBar::calculate_h() + ys5;
752 for( int i=0; i<nbars; ++i, y+=dy )
753 add_tool(bars[i] = new ScanStatusBar(x, y, get_w() - xs20, yS(100)));
755 add_tool(new BC_CancelButton(this));
760 ScanStatus(Commercials *commercials, int x, int y,
761 int nlines, int nbars, int &status, const char *text)
765 this->commercials = commercials;
766 gui = new ScanStatusGUI(this, x, y, nlines, nbars);
767 gui->create_objects(text);
781 update_length(int i, int64_t length)
783 if( status ) return 1;
784 gui->bars[i]->update_length(length);
789 update_position(int i, int64_t position)
791 if( status ) return 1;
792 gui->bars[i]->update(position);
797 update_text(int i, const char *text)
799 if( status ) return 1;
800 gui->texts[i]->update(text);
809 if( gui ) gui->set_done(1);
818 gui->create_objects(_("Cutting Ads"));
819 int result = gui->run_window();
820 if( result ) status = 1;
825 void SdbPacketQueue::
826 put_packet(SdbPacket *p)
828 lock("SdbPacketQueue::put_packet");
833 SdbPacket *SdbPacketQueue::
836 lock("SdbPacketQueue::get_packet");
837 SdbPacket *p = first;
848 input_lock = new Condition(0, "SkimDbThread::input_lock");
849 for( int i=32; --i>=0; ) skim_frames.append(new SdbSkimFrame(this));
865 start(Commercials *commercials)
867 commercials->add_user();
868 this->commercials = commercials;
869 if( commercials->openDb() ) return;
870 if( commercials->detachDb() ) return;
880 input_lock->unlock();
884 if( commercials && !commercials->remove_user() )
889 skim(int pid,int64_t framenum,double framerate,
890 uint8_t *idata,int mw,int mh,int iw,int ih)
892 SdbSkimFrame *sf = (SdbSkimFrame *)skim_frames.get_packet();
893 if( !sf ) { printf("SkimDbThread::skim no packet\n"); return 1; }
894 sf->load(pid,framenum,framerate, idata,mw,mh,iw,ih);
900 void SdbPacket::start()
902 thread->put_packet(this);
906 put_packet(SdbPacket *p)
908 active_packets.put_packet(p);
909 input_lock->unlock();
916 input_lock->lock("SkimDbThread::run");
918 SdbPacket *p = active_packets.get_packet();
920 commercials->attachDb();
922 commercials->detachDb();
928 load(int pid,int64_t framenum,double framerate,
929 uint8_t *idata,int mw,int mh,int iw,int ih)
931 int sw=SWIDTH, sh=SHEIGHT;
933 this->framenum = framenum;
934 this->framerate = framerate;
935 Scale(idata,0,mw,mh,0,0,iw/8,ih/8).scale(dat,sw,sh,0,0,sw,sh);
941 double position = framenum / framerate;
942 thread->commercials->skim_frame(thread->snips, dat, position);
943 thread->skim_frames.put_packet(this);
947 void run_that_puppy(const char *fn)
949 FILE *fp = fopen(fn,"r"); double position, length;
950 if( !fp ) { perror("fopen"); return; }
951 MWindow::commercials->resetDb();
952 MWindow *mwindow = MWindow::commercials->mwindow;
953 File *file = mwindow->video_cache->first->file;
955 while( !result && fscanf(fp,"%lf %lf\n",&position, &length) == 2 ) {
956 int result = MWindow::commercials->put_clip(file, 0, position, length);
957 printf(_("cut %f/%f = %d\n"),position,length, result);
959 MWindow::commercials->commitDb();
960 MWindow::commercials->closeDb();