Credit Andrew - improve in-tree documentation
[goodguy/cinelerra.git] / cinelerra / commercials.C
1
2 #include "arraylist.h"
3 #include "asset.h"
4 #include "clip.h"
5 #include "commercials.h"
6 #include "cache.h"
7 #include "edit.h"
8 #include "edits.h"
9 #include "edl.h"
10 #include "edlsession.h"
11 #include "file.h"
12 #include "filexml.h"
13 #include "indexable.h"
14 #include "indexfile.h"
15 #include "linklist.h"
16 #include "mainmenu.h"
17 #include "mediadb.h"
18 #include "mwindow.h"
19 #include "mwindowgui.h"
20 #include "pluginset.h"
21 #include "preferences.h"
22 #include "record.h"
23 #include "track.h"
24 #include "tracks.h"
25 #include "vframe.h"
26
27 #include <stdio.h>
28 #include <stdarg.h>
29 #include <stdlib.h>
30 #include <stdint.h>
31 #include <unistd.h>
32 #include <sys/types.h>
33 #include <sys/wait.h>
34
35
36
37 Commercials::
38 Commercials(MWindow *mwindow)
39  : Garbage("Commercials")
40 {
41         this->mwindow = mwindow;
42         this->scan_status = 0;
43         mdb = new MediaDb();
44         scan_file = 0;
45         cancelled = 0;
46         muted = 0;
47         openDb();
48         detachDb();
49 }
50
51 Commercials::
52 ~Commercials()
53 {
54         closeDb();
55         delete mdb;
56         tracks.remove_all_objects();
57 }
58
59 int Commercials::
60 newDb()
61 {
62         return mdb->newDb();
63 }
64
65 void Commercials::
66 closeDb()
67 {
68         tracks.remove_all_objects();
69         mdb->closeDb();
70 }
71
72 int Commercials::
73 openDb()
74 {
75         if( !mwindow->has_commercials() )
76                 return -1;
77         if( !mdb->is_open() && mdb->openDb() ) {
78                 printf("Commercials::openDb failed\n");
79                 return -1;
80         }
81         return 0;
82 }
83
84 int Commercials::
85 resetDb()
86 {
87         mdb->closeDb();
88         if( !mwindow->has_commercials() )
89                 return -1;
90         return mdb->resetDb();
91 }
92
93 void Commercials::
94 commitDb()
95 {
96         mdb->commitDb();
97 }
98
99 void Commercials::
100 undoDb()
101 {
102         mdb->undoDb();
103 }
104
105 int Commercials::
106 attachDb(int rw)
107 {
108         return mdb->attachDb(rw);
109 }
110
111 int Commercials::
112 detachDb()
113 {
114         return mdb->detachDb();
115 }
116
117 int Commercials::
118 put_weight(VFrame *frame, int no)
119 {
120         int w = frame->get_w(), h = frame->get_h(), rsz = w;
121         uint8_t *tp = frame->get_y();
122         int64_t wt = 0;
123         for( int y=h; --y>=0; tp+=rsz ) {
124                 uint8_t *bp = tp;
125                 for( int x=w; --x>=0; ++bp ) wt += *bp;
126         }
127         clip_weights[no] = (double)wt / (w*h);
128         return 0;
129 }
130
131 int Commercials::
132 put_frame(VFrame *frame, int no, int group, double offset)
133 {
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
140         return ret;
141 }
142
143 int Commercials::
144 put_clip(File *file, int track, double position, double length)
145 {
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;
160
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;
164
165         clip_id = mdb->clip_id();
166         cancelled = 0;
167         scan_status = new ScanStatus(this, 30, 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);
172
173         clip_weights = mdb->clip_weights();
174         frame_period = 1. / framerate;
175         VFrame frame(width, height, BC_YUV420P);
176
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);
184         }
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);
190                 ++n;
191         }
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);
198         }
199
200         double wt = 0;
201         for( i=0; i<frames; ++i ) wt += clip_weights[i];
202         mdb->clip_average_weight(wt/frames);
203
204         delete scan_status;
205         scan_status = 0;
206         return result;
207 }
208
209
210 Clips *Commercials::
211 find_clips(int pid)
212 {
213         Clips *clips = 0;
214         int trk = tracks.size();
215         while( --trk >= 0 && (clips=tracks.get(trk))->pid != pid );
216         return trk >= 0 ? clips : 0;
217 }
218
219 int Commercials::
220 get_frame(File *file, int pid, double position,
221         uint8_t *tp, int mw, int mh, int ww, int hh)
222 {
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);
230         return result;
231 }
232
233 double Commercials::
234 frame_weight(uint8_t *tdat, int rowsz, int width, int height)
235 {
236         int64_t weight = 0;
237         for( int y=height; --y>=0; tdat+=rowsz ) {
238                 uint8_t *bp = tdat;
239                 for( int x=width; --x>=0; ++bp ) weight += *bp;
240         }
241         return (double)weight / (width*height);
242 }
243
244 int Commercials::
245 skim_frame(Snips *snips, uint8_t *dat, double position)
246 {
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 ) {
250                 next = clip->next;
251                 result = verify_snip((Snip*)clip, weight, position);
252                 if( !result ) {
253                         if( clip->votes > 2 )
254                                 mute_audio(clip);
255                 }
256                 else if( result < 0 || --clip->votes < 0 ) {
257                         --snips->count;
258                         delete clip;
259                         if( !snips->first )
260                                 unmute_audio();
261                 }
262         }
263         return 0;
264 }
265
266 double Commercials::
267 abs_err(Snip *snip, double *ap, int j, int len, double iframerate)
268 {
269         double vv = 0;
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);
277                 vv += dv;
278                 ++n;
279         }
280         return !n ? MEDIA_WEIGHT_ERRLMT : vv / n;
281 }
282
283 int Commercials::
284 verify_snip(Snip *snip, double weight, double position)
285 {
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;
298         int k = 0;
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; }
303         }
304         if( kerr >= errlmt ) return 1;
305         if( iframe_no + k >= iframes ) return -1;
306         return 0;
307 }
308
309
310 int Commercials::
311 mute_audio(Clip *clip)
312 {
313         Record *record = mwindow->gui->record;
314         if( !clip->muted ) {
315                 clip->muted = 1;
316                 mdb->access_clip(clip->clip_id);
317                 mdb->commitDb();
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.);
322         }
323         if( !muted ) {
324                 muted = 1;
325                 record->set_mute_gain(0);
326                 printf(_("***MUTE***\n"));
327         }
328         return 0;
329 }
330
331 int Commercials::
332 unmute_audio()
333 {
334         Record *record = mwindow->gui->record;
335         if( muted ) {
336                 muted = 0;
337                 record->set_mute_gain(1);
338                 printf(_("***UNMUTE***\n"));
339         }
340         record->undisplay_vframe();
341         return 0;
342 }
343
344 int Commercials::skim_weight(int track)
345 {
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;
363 }
364
365 int Commercials::skim_weight(void *vp, int track)
366 {
367         return ((Commercials *)vp)->skim_weight(track);
368 }
369
370
371 int Commercials::
372 skim_weights(int track, double position, double iframerate, int iframes)
373 {
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;
380         // skimming limits
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);
388 }
389
390 double Commercials::
391 abs_err(double *ap, int len, double iframerate)
392 {
393         double vv = 0;
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);
401                 vv += dv;
402                 ++n;
403         }
404         return !n ? MEDIA_WEIGHT_ERRLMT : vv / n;
405 }
406
407
408 int Commercials::
409 verify_clip(int clip_id, int track, double position, double &pos)
410 {
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;
423         int n = 2*tmargin;
424         for( int j=1; j<n; ++j ) {
425                 err = abs_err(iweights+j, mframes, iframerate);
426                 if( err < kerr ) { kerr = err;  k = j; }
427         }
428 //printf("** kerr %f, k=%d\n", kerr, k);
429         if( kerr >= MEDIA_WEIGHT_ERRLMT ) return -1;
430         pos = position + (k - tmargin) / iframerate;
431         return 0;
432 }
433
434 int Commercials::
435 write_ads(const char *filename)
436 {
437         char index_filename[BCTEXTLEN], source_filename[BCTEXTLEN];
438         IndexFile::get_index_filename(source_filename,
439                 mwindow->preferences->index_directory, index_filename,
440                 filename, ".ads");
441
442         FILE *fp = fopen(index_filename, "wb");
443         if( fp != 0 ) {
444                 FileXML xml;
445                 xml.tag.set_title("ADS");
446                 xml.append_tag();
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");
452                 xml.append_tag();
453                 xml.append_newline();
454                 xml.terminate_string();
455                 xml.write_to_file(fp);
456                 fclose(fp);
457         }
458         return 0;
459 }
460
461 int Commercials::
462 read_ads(const char *filename)
463 {
464         char index_filename[BCTEXTLEN], source_filename[BCTEXTLEN];
465         IndexFile::get_index_filename(source_filename,
466                 mwindow->preferences->index_directory, index_filename,
467                 filename, ".ads");
468         tracks.remove_all_objects();
469         FileXML xml;
470         if( xml.read_from_file(index_filename, 1) ) return 1;
471
472         do {
473                 if( xml.read_tag() ) return 1;
474         } while( !xml.tag.title_is("ADS") );
475
476         for(;;) {
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;
485                 }
486         }
487
488         return 0;
489 }
490
491 void Commercials::
492 dump_ads()
493 {
494         for( int i=0; i<tracks.size(); ++i )
495         {
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);
501         }
502 }
503
504 int Commercials::
505 verify_edit(Track *track, Edit *edit, double start, double end)
506 {
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;
513         return 0;
514 }
515
516 Edit *Commercials::
517 cut_edit(Track *track, Edit *edit, int64_t clip_start, int64_t clip_end)
518 {
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);
533                 }
534         }
535         // cut edit
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;
542         return next_edit;
543 }
544
545 int Commercials::
546 scan_audio(int vstream, double start, double end)
547 {
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->record ) continue;
555                 Edits *edits = atrk->edits;  Edit *next = 0;
556                 for( Edit *edit=edits->first; !cancelled && edit; edit=next ) {
557                         next = 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));
567                 }
568                 edits->optimize();
569         }
570         return 0;
571 }
572
573 int Commercials::
574 scan_media()
575 {
576         cancelled = 0;
577         scan_status = new ScanStatus(this, 30, 30, 2, 2,
578                 cancelled, _("Cutting Ads"));
579         if( !openDb() ) {
580                 scan_video();
581                 commitDb();
582                 closeDb();
583         }
584         delete scan_status;
585         scan_status = 0;
586         return 0;
587 }
588
589 int Commercials::
590 scan_video()
591 {
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->record ) continue;
596                 Edits *edits = vtrk->edits;  Edit *next = 0;
597                 for( Edit *edit=edits->first; !cancelled && edit; edit=next ) {
598                         next = 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;
606                         next = edits->first;
607                 }
608                 edits->optimize();
609         }
610         return 0;
611 }
612
613 int Commercials::
614 scan_asset(Asset *asset, Track *vtrk, Edit *edit)
615 {
616         int result = 1;
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);
621                 scan_file = 0;
622         }
623         return result;
624 }
625
626 int Commercials::
627 scan_clips(Track *vtrk, Edit *edit)
628 {
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;
645                 if( start < 0 ) {
646                         if( (length+=start) <= 0 ) continue;
647                         start = 0;
648                 }
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);
659                 last_end = end;
660         }
661         return 1;
662 }
663
664
665 int Commercials::
666 update_cut_info(int trk, double position)
667 {
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);
674         return 0;
675 }
676
677 int Commercials::
678 update_caption(int trk, int edt, const char *path)
679 {
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);
684         return 0;
685 }
686
687 int Commercials::
688 update_status(int clip, double start, double end)
689 {
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);
694         return 0;
695 }
696
697
698 ScanStatusGUI::
699 ScanStatusGUI(ScanStatus *sswindow, int x, int y, int nlines, int nbars)
700  : BC_Window(_("Scanning"), x, y, 340,
701         40 + BC_CancelButton::calculate_h() +
702                 (BC_Title::calculate_h((BC_WindowBase*) sswindow->
703                         commercials->mwindow->gui, _("My")) + 5) * nlines +
704                 (BC_ProgressBar::calculate_h() + 5) * nbars, 0, 0, 0)
705 {
706         this->sswindow = sswindow;
707         this->nlines = nlines;
708         this->nbars = nbars;
709         this->texts = new BC_Title *[nlines];
710         this->bars = new ScanStatusBar *[nbars];
711 }
712
713 ScanStatusGUI::
714 ~ScanStatusGUI()
715 {
716         delete [] texts;
717         delete [] bars;
718 }
719
720 void ScanStatusGUI::
721 create_objects(const char *text)
722 {
723         lock_window("ScanStatusGUI::create_objects");
724         int x = 10, y = 10;
725         int dy = BC_Title::calculate_h((BC_WindowBase*) sswindow->
726                 commercials->mwindow->gui, "My") + 5;
727         for( int i=0; i<nlines; ++i, y+=dy, text="" )
728                 add_tool(texts[i] = new BC_Title(x, y, text));
729         y += 10;
730         dy = BC_ProgressBar::calculate_h() + 5;
731         for( int i=0; i<nbars; ++i, y+=dy )
732                 add_tool(bars[i] = new ScanStatusBar(x, y, get_w() - 20, 100));
733
734         add_tool(new BC_CancelButton(this));
735         unlock_window();
736 }
737
738 ScanStatus::
739 ScanStatus(Commercials *commercials, int x, int y,
740         int nlines, int nbars, int &status, const char *text)
741  : Thread(1, 0, 0),
742    status(status)
743 {
744         this->commercials = commercials;
745         gui = new ScanStatusGUI(this, x, y, nlines, nbars);
746         gui->create_objects(text);
747         start();
748         gui->init_wait();
749 };
750
751 ScanStatus::
752 ~ScanStatus()
753 {
754         stop();
755         delete gui;
756 }
757
758
759 int ScanStatus::
760 update_length(int i, int64_t length)
761 {
762         if( status ) return 1;
763         gui->bars[i]->update_length(length);
764         return 0;
765 }
766
767 int ScanStatus::
768 update_position(int i, int64_t position)
769 {
770         if( status ) return 1;
771         gui->bars[i]->update(position);
772         return 0;
773 }
774
775 int ScanStatus::
776 update_text(int i, const char *text)
777 {
778         if( status ) return 1;
779         gui->texts[i]->update(text);
780         return 0;
781 }
782
783 void ScanStatus::
784 stop()
785 {
786         status = 1;
787         if( running() ) {
788                 if( gui ) gui->set_done(1);
789                 cancel();
790         }
791         join();
792 }
793
794 void ScanStatus::
795 run()
796 {
797         gui->create_objects(_("Cutting Ads"));
798         int result = gui->run_window();
799         if( result ) status = 1;
800 }
801
802
803
804 void SdbPacketQueue::
805 put_packet(SdbPacket *p)
806 {
807         lock("SdbPacketQueue::put_packet");
808         append(p);
809         unlock();
810 }
811
812 SdbPacket *SdbPacketQueue::
813 get_packet()
814 {
815         lock("SdbPacketQueue::get_packet");
816         SdbPacket *p = first;
817         remove_pointer(p);
818         unlock();
819         return p;
820 }
821
822
823 SkimDbThread::
824 SkimDbThread()
825  : Thread(1, 0, 0)
826 {
827         input_lock = new Condition(0, "SkimDbThread::input_lock");
828         for( int i=32; --i>=0; ) skim_frames.append(new SdbSkimFrame(this));
829         snips = new Snips();
830         commercials = 0;
831         done = 1;
832 }
833
834 SkimDbThread::
835 ~SkimDbThread()
836 {
837         stop();
838         delete snips;
839         delete input_lock;
840 }
841
842
843 void SkimDbThread::
844 start(Commercials *commercials)
845 {
846         commercials->add_user();
847         this->commercials = commercials;
848         if( commercials->openDb() ) return;
849         if( commercials->detachDb() ) return;
850         done = 0;
851         Thread::start();
852 }
853
854 void SkimDbThread::
855 stop()
856 {
857         if( running() ) {
858                 done = 1;
859                 input_lock->unlock();
860                 cancel();
861         }
862         join();
863         if( commercials && !commercials->remove_user() )
864                 commercials = 0;
865 }
866
867 int SkimDbThread::
868 skim(int pid,int64_t framenum,double framerate,
869         uint8_t *idata,int mw,int mh,int iw,int ih)
870 {
871         SdbSkimFrame *sf = (SdbSkimFrame *)skim_frames.get_packet();
872         if( !sf ) { printf("SkimDbThread::skim no packet\n");  return 1; }
873         sf->load(pid,framenum,framerate, idata,mw,mh,iw,ih);
874         sf->start();
875         return 0;
876 }
877
878
879 void SdbPacket::start()
880 {
881         thread->put_packet(this);
882 }
883
884 void SkimDbThread::
885 put_packet(SdbPacket *p)
886 {
887         active_packets.put_packet(p);
888         input_lock->unlock();
889 }
890
891 void SkimDbThread::
892 run()
893 {
894         while( !done ) {
895                 input_lock->lock("SkimDbThread::run");
896                 if( done ) break;
897                 SdbPacket *p = active_packets.get_packet();
898                 if( !p ) continue;
899                 commercials->attachDb();
900                 p->run();
901                 commercials->detachDb();
902         }
903 }
904
905
906 void SdbSkimFrame::
907 load(int pid,int64_t framenum,double framerate,
908         uint8_t *idata,int mw,int mh,int iw,int ih)
909 {
910         int sw=SWIDTH, sh=SHEIGHT;
911         this->pid = pid;
912         this->framenum = framenum;
913         this->framerate = framerate;
914         Scale(idata,0,mw,mh,0,0,iw/8,ih/8).scale(dat,sw,sh,0,0,sw,sh);
915 }
916
917 void SdbSkimFrame::
918 run()
919 {
920         double position = framenum / framerate;
921         thread->commercials->skim_frame(thread->snips, dat, position);
922         thread->skim_frames.put_packet(this);
923 }
924
925
926 void run_that_puppy(const char *fn)
927 {
928         FILE *fp = fopen(fn,"r");  double position, length;
929         if( !fp ) { perror("fopen"); return; }
930         MWindow::commercials->resetDb();
931         MWindow *mwindow = MWindow::commercials->mwindow;
932         File *file = mwindow->video_cache->first->file;
933         int result = 0;
934         while( !result && fscanf(fp,"%lf %lf\n",&position, &length) == 2 ) {
935                 int result = MWindow::commercials->put_clip(file, 0, position, length);
936                 printf(_("cut %f/%f = %d\n"),position,length, result);
937         }
938         MWindow::commercials->commitDb();
939         MWindow::commercials->closeDb();
940         fclose(fp);
941 }
942