colorpick rework, color popup for titler, color convert fixes
[goodguy/history.git] / cinelerra-5.1 / cinelerra / cutads.C
1 #include "../libzmpeg3/libzmpeg3.h"
2 #include "arraylist.h"
3 #include "mediadb.h"
4
5 #include <stdio.h>
6 #include <stdint.h>
7 #include <stdlib.h>
8 #include <stdarg.h>
9 #include <string.h>
10 #include <unistd.h>
11 #include <signal.h>
12 #include <fcntl.h>
13 #include <time.h>
14 #include <math.h>
15
16 #include <sys/time.h>
17 #include <sys/resource.h>
18
19 // c++ `cat x86_64/c_flags` -c -o x86_64/cutads.o cutads.C
20 // c++ -pthread -o x86_64/cutads x86_64/cutads.o x86_64/mediadb.o x86_64/filexml.o
21 //    ../libzmpeg3/x86_64/libzmpeg3.a ../db/x86_64/db.a -lX11
22
23 using namespace std;
24 #define fail(s) do { printf("fail %s%s:%d\n",__func__,#s,__LINE__); return 1; } while(0)
25
26 /* missing from system headers, no /usr/include <linux/ioprio.h>
27  *   IOPRIO_WHO_PROCESS, IOPRIO_CLASS_SHIFT, IOPRIO_CLASS_IDLE */
28 enum { IOPRIO_CLASS_NONE, IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE, };
29 #define IO_CLASS(n) (((int)(n)) << 13)
30 #define IO_WHO_PROCESS  1
31 #include <sys/syscall.h>
32 #include <asm/unistd.h>
33
34 // commercial edge detection:
35 // must have audio < min_audio
36 //   and within +- check_margin seconds of low audio
37 //   must have video < min_video or delta video > min_dvideo
38
39 static double min_audio = 0.5e-3 * 32767;   // (-63db quite quiet)
40 static double min_video = 0.1 * 255;        // (pretty dark)
41 static double min_dvideo = 0.1 * 255;       // (gittery)
42 static double lo_video = 0.075 * 255;       // (dark)
43 static double hi_video = 0.925 * 255;       // (light)
44 static double check_margin = 1.0;           // t-1.0..t+1.0 secs
45 static double min_clip_time = 3;            // ignore clips shorter than this
46 static int    video_cutoff = 15;            // video outside color space
47
48 MediaDb *db = 0;
49
50 class Src;
51 class Weights;
52 class MarginFrame;
53 class Margin;
54 class Video;
55 class Audio;
56 class Scan;
57
58 static int ioprio_set(int which, int who, int ioprio)
59 {
60         return syscall(SYS_ioprio_set, which, who, ioprio);
61 }
62
63 static inline int clip(int v, int mn, int mx)
64 {
65   return v<mn ? mn : v>mx ? mx : v;
66 }
67
68
69
70 class Src
71 {
72         char path[1024];
73         zmpeg3_t* zsrc;
74 public:
75         bool open() { return zsrc != 0; }
76         operator zmpeg3_t *() { return zsrc; }
77         zmpeg3_t *operator ->() { return zsrc; }
78         const char *asset_path() { return path; }
79
80         Src(const char *fn) {
81                 strcpy(path, fn);
82                 int ret;  zsrc = new zmpeg3_t(path, ret,
83                         ZIO_UNBUFFERED+ZIO_SINGLE_ACCESS);
84                 if( ret ) { delete zsrc; zsrc = 0; }
85         }
86         ~Src() { delete zsrc; }
87 };
88
89
90
91 class Weights
92 {
93         int cur;
94         ArrayList<double> positions;
95         ArrayList<double> values;
96 public:
97         int prev() { return cur > 0 ? --cur : -1; }
98         int next() { return cur < positions.size()-1 ? ++cur : -1; }
99         double position() { return positions.get(cur); }
100         double value() { return values.get(cur); }
101         double next_value() { return values.get(cur++); }
102         int length() { return values.size() - cur; }
103         int find(double pos);
104         int locate(double pos);
105         double err(double start, double len, double irate,
106                 double *iweights, int iframes);
107         void add(double p, double v) { positions.append(p); values.append(v); }
108         int minimum_forward(double start, double end, double &time);
109         int minimum_backward(double start, double end, double &time);
110         int video_forward(double start, double end, double &time);
111         int audio_forward(double start, double end, double &time);
112         int video_backward(double start, double end, double &time);
113         int audio_backward(double start, double end, double &time);
114         double get_weight(double pos);
115         double average(double pos, int len);
116         int save(double pos, double *wp, int len);
117         void write(const char *fn);
118         Weights() : cur(0) {}
119         ~Weights() {}
120 };
121
122 int Weights::find(double pos)
123 {
124         int l = -1;
125         int r = positions.size();
126         while( (r - l) > 1 ) {
127                 int i = (l+r) >> 1;
128                 double v = positions.get(i);
129                 if( pos == v ) return i;
130                 if( pos > v ) l = i; else r = i;
131         }
132         return l;
133 }
134
135 int Weights::locate(double pos)
136 {
137         int ret = find(pos);
138         if( ret < 0 ) ret = 0;
139         return cur = ret;
140 }
141
142 double Weights::
143 err(double pos, double len, double irate,
144         double *iweights, int iframes)
145 {
146 // trim leading / trailing dark video
147         locate(pos);
148         if( position() > pos ) return 256.;
149         double end_pos = pos + len;
150         while( position() < end_pos ) {
151                 if( value() >= min_video ) break;
152                 if( next() < 0 ) return 256.;
153         }
154         int l = 0, n = 0;
155         double vv = 0, lvv = 0, dd = 0, ldd = 0, ldv = 0;
156         while( position() < end_pos ) {
157                 int k = (position()-pos) * irate + 1./1001+0.5;
158                 if( k >= 0 ) {
159                         if( k >= iframes ) break;
160                         double v = value(), dv = v - iweights[k];
161                         vv += fabs(dv);
162                         dd += fabs(dv - ldv);  ldv = dv;
163                         if( v > min_video ) { lvv = vv; ldd = dd; l = n; }
164                         ++n;
165                 }
166                 if( next() < 0 ) break;
167         }
168
169         if( ldd < lvv && lvv < MEDIA_MEAN_ERRLMT*l ) lvv = ldd;
170         return !l ? MEDIA_WEIGHT_ERRLMT : lvv / l;
171 }
172
173 double Weights::
174 get_weight(double pos)
175 {
176         locate(pos);
177         return position() > pos ? -1 : value();
178 }
179
180 double Weights::
181 average(double pos, int len)
182 {
183         locate(pos);
184         int n = length();
185         if( len < n ) n = len;
186         double v = 0;
187         for( int i=len; --i>=0; ) v += next_value();
188         return n > 0 ? v / n : 256;
189 }
190
191 int Weights::
192 save(double pos, double *wp, int len)
193 {
194         locate(pos);
195         if( position() > pos ) fail();
196         if( length() < len ) fail();
197         for( int i=len; --i>=0; ++wp ) *wp = next_value();
198         return 0;
199 }
200
201 void Weights::
202 write(const char *fn)
203 {
204         FILE *fp = fopen(fn,"w");
205         if( fp ) {
206                 locate(0);
207                 if( length() > 0 ) do {
208                         fprintf(fp,"%f %f\n",position(),value());
209                 } while( next() >= 0 );
210                 fclose(fp);
211         }
212 }
213
214
215
216 class Video
217 {
218         zmpeg3_t *src;
219         char *yp, *up, *vp;
220         int trk, trks;
221         int zpid, w, h, ww, hh, is_scaled;
222         Weights *weights;
223         double rate, vorigin;
224         int64_t len, pos;
225         uint8_t sbfr[SFRM_SZ];
226         char name[64];
227         Margin *margin;
228
229         void init();
230 public:
231         uint8_t *get_y() { return (uint8_t*)yp; }
232         uint8_t *get_u() { return (uint8_t*)up; }
233         uint8_t *get_v() { return (uint8_t*)vp; }
234         int tracks() { return trks; }
235         int track() { return trk; }
236         int width() { return w; }
237         int height() { return h; }
238         int coded_width() { return ww; }
239         int coded_height() { return hh; }
240         int pid() { return zpid; }
241         bool eof() { return src->end_of_video(trk); }
242         double time() { return src->get_video_time(trk); }
243         void set_title(char *cp) { strcpy(name,cp); }
244         char *title() { return name; }
245         double framerate() { return rate; }
246         int64_t frame_count(double position) { return position*rate + 1./1001; }
247         double frame_position(int64_t count) { return count / rate; }
248         void set_origin(double t) { vorigin = t; }
249         double origin() { return vorigin; }
250         int64_t length() { return len; }
251         int64_t frame_no() { return pos; }
252         void disable_weights() { delete weights;  weights = 0; }
253         void enable_weights() { weights = new Weights(); }
254         void write_weights(const char *fn) { weights->write(fn); }
255         void margin_frames(Margin *mp=0) { margin = mp; }
256         int forward(double start, double end, double &time) {
257                 return weights->video_forward(start, end, time);
258         }
259         int backward(double start, double end, double &time) {
260                 return weights->video_backward(start, end, time);
261         }
262         double err(double pos, double len, double irate,
263                 double *iweights, int iframes) {
264                 return weights->err(pos, len, irate, iweights, iframes);
265         }
266         double frame_weight(double pos) {
267                 return weights->get_weight(pos);
268         }
269         double average_weight(double pos, double len) {
270                 return weights->average(pos, len*rate);
271         }
272         int save_weights(double pos, double *wp, int len) {
273                 return weights->save(pos, wp, len);
274         }
275         void init(zmpeg3_t *zsrc, int ztrk);
276         int load_frame();
277         int read_frame();
278         uint8_t *scaled();
279         double raw_weight();
280         double weight(uint8_t *bp);
281
282         Video() { weights = 0; }
283         ~Video() { delete weights; }
284 } video;
285
286 void Video::
287 init(zmpeg3_t *zsrc, int ztrk)
288 {
289         src = zsrc;  trk = ztrk;
290         trks = src->total_vstreams();
291         zpid = src->video_pid(trk);
292         rate = src->frame_rate(trk);
293         w = src->video_width(trk);
294         h = src->video_height(trk);
295         ww = src->coded_width(trk);
296         hh = src->coded_height(trk);
297         name[0] = 0;
298         margin = 0;
299         is_scaled = 0;
300         vorigin = -1;
301         len = src->video_frames(trk);
302         pos = 0;
303 }
304
305 int Video::load_frame()
306 {
307         ++pos;  is_scaled = 0;
308         return src->read_yuvframe_ptr(&yp, &up, &vp,trk);
309 }
310
311
312
313 class MarginFrame
314 {
315         uint8_t sbfr[SFRM_SZ];
316 public:
317         uint8_t *frame() { return sbfr; }
318         MarginFrame(uint8_t *bp) { memcpy(sbfr,bp,sizeof(sbfr)); }
319 };
320
321 class Margin : public ArrayList<MarginFrame*>
322 {
323         int cur;
324         int64_t start_frame;
325         double start_time;
326 public:
327         int locate(double t);
328         void copy(Margin *that);
329         double position() { return start_time + video.frame_position(cur); }
330         int check(Clips *clips, double start, double end, int group_mask);
331         int length() { return size() - cur; }
332         uint8_t *get_frame() { return get(cur++)->frame(); }
333         void add(uint8_t *sbfr) { append(new MarginFrame(sbfr)); }
334
335         Margin(int64_t frame, double time) : cur(0) {
336                 start_frame = frame;  start_time = time;
337         }
338         ~Margin() { remove_all_objects(); }
339 } *prefix = 0, *suffix = 0;
340
341
342 int Margin::
343 locate(double t)
344 {
345         int pos = video.frame_count(t-start_time);
346         if( pos < 0 || pos >= size() ) return -1;
347         return cur = pos;
348 }
349
350 void Margin::
351 copy(Margin *that)
352 {
353         this->start_frame = that->start_frame + cur;
354         this->start_time = that->position();
355         while( that->length() > 0 ) add(that->get_frame());
356 }
357
358 int Margin::
359 check(Clips *clips, double start, double end, int group_mask)
360 {
361         if( locate(start) < 0 ) fail();
362         double pos;
363         while( length() > 0 && (pos=position()) < end ) {
364                 uint8_t *sbfr = get_frame();  int fid;
365 // write_pbm(sbfr,SWIDTH,SHEIGHT,"/tmp/dat/f%07.3f.pbm",pos-video.origin());
366                 db->get_frame_key(sbfr, fid, clips, pos, group_mask);
367         }
368         return 0;
369 }
370
371
372 int Video::read_frame()
373 {
374         if( load_frame() ) fail();
375 // write_pbm(get_y(),width(),height(),"/tmp/dat/f%05ld.pbm",pos);
376         if( margin ) margin->add(scaled());
377         if( weights ) weights->add(time(), weight(scaled()));
378         return 0;
379 }
380
381 uint8_t *Video::scaled()
382 {
383         if( !is_scaled ) {
384                 is_scaled = 1;
385                 uint8_t *yp = get_y();  int sh = hh;
386 //              while( sh>h && *yp<video_cutoff ) { yp += ww;  --sh; }
387 //static int fno = 0;
388 //write_pbm(yp,ww,hh,"/tmp/data/f%04d.pbm",fno);
389                 Scale(yp,0,ww,sh, 0,0,w,h).
390                         scale(sbfr,SWIDTH,SHEIGHT, 0,0,SWIDTH,SHEIGHT);
391 //write_pbm(sbfr,SWIDTH,SHEIGHT,"/tmp/data/s%04d.pbm",fno);
392 //++fno;
393         }
394         return sbfr;
395 }
396
397 double Video::raw_weight()
398 {
399         uint8_t *yp = get_y();
400         int64_t wt = 0;
401         for( int y=h; --y>=0; yp+=ww ) {
402                 uint8_t *bp = yp;  int n = 0;
403                 for( int x=w; --x>=0; ++bp ) n += *bp;
404                 wt += n;
405         }
406         return (double)wt / (w*h);
407 }
408
409 double Video::weight(uint8_t *bp)
410 {
411         int64_t wt = 0;
412         for( int i=SFRM_SZ; --i>=0; ++bp ) wt += *bp;
413         return (double)wt / SFRM_SZ;
414 }
415
416 int Weights::
417 minimum_forward(double start, double end, double &time)
418 {
419         locate(start);
420         double t = position();
421         double min = value();
422         while( next() >= 0 && position() < end ) {
423                 if( value() > min ) continue;
424                 min = value();  t = position();
425         }
426         time = t;
427         return 0;
428 }
429
430 int Weights::
431 minimum_backward(double start, double end, double &time)
432 {
433         locate(end);
434         double t = position();
435         double min = value();
436         while( prev() >= 0 && position() >= start ) {
437                 if( value() > min ) continue;
438                 min = value();  t = position();
439         }
440         time = t;
441         return 0;
442 }
443
444 // find trailing edge scanning backward
445 //   must have video < min_video or delta video > min_dvideo
446 int Weights::
447 video_backward(double start, double end, double &time)
448 {
449         locate(end);
450         double t = position(), v = value();
451         for(;;) {
452                 if( t < start ) return 1;
453                 double vv = v;  v = value();
454                 if( v <= min_video ) break;
455                 if( fabs(vv-v) >= min_dvideo ) break;
456                 if( prev() < 0 ) fail();
457                 t = position();
458         }
459         while( prev() >= 0 ) {
460                 if( position() < start ) break;
461                 t = position();
462                 if( value() > min_video ) break;
463         }
464         time = t;
465         return 0;
466 }
467
468 // find leading edge scanning backward
469 //   must have audio < min_audio
470 int Weights::
471 audio_backward(double start, double end, double &time)
472 {
473         locate(end);
474         double t = position();
475         for(;;) {
476                 if( t < start ) fail();
477                 if( value() <= min_audio ) break;
478                 if( prev() < 0 ) fail();
479                 t = position();
480         }
481         time = t;
482         return 0;
483 }
484
485 // find trailing edge scanning forward
486 //   must have video < min_video or delta video > min_dvideo
487 int Weights::
488 video_forward(double start, double end, double &time)
489 {
490         locate(start);
491         double t = position(), v = value();
492         for(;;) {
493                 if( t >= end ) return 1;
494                 double vv = v;  v = value();
495                 if( v <= min_video ) break;
496                 if( fabs(vv-v) >= min_dvideo ) break;
497                 if( next() < 0 ) fail();
498                 t = position();
499         }
500         while( next() >= 0 ) {
501                 if( position() >= end ) break;
502                 t = position();
503                 if( value() > min_video ) break;
504         }
505         time = t;
506         return 0;
507 }
508
509 // find leading edge scanning forward
510 //   must have audio < min_audio
511 int Weights::
512 audio_forward(double start, double end, double &time)
513 {
514         locate(start);
515         double t = position();
516         for(;;) {
517                 if( t >= end ) fail();
518                 if( value() <= min_audio ) break;
519                 if( next() < 0 ) fail();
520                 t = position();
521         }
522         time = t;
523         return 0;
524 }
525
526
527
528 class Audio {
529         zmpeg3_t *src;
530         int trk, trks;
531         int chans, rate;
532         int64_t len, pos;
533         Weights *weights;
534         int bfr_sz;
535         short *bfr;
536
537 public:
538         int tracks() { return trks; }
539         int track() { return trk; }
540         int channels() { return chans; }
541         int samplerate() { return rate; }
542         int64_t length() { return len; }
543         int64_t sample_no() { return pos; }
544         bool eof() { return src->end_of_audio(trk); }
545         double time() { return src->get_audio_time(trk); }
546         void set_buffer(int n) {
547                 delete [] bfr;
548                 bfr = new short[bfr_sz = clip(n,256,32767)];
549         }
550         void disable_weights() { delete weights;  weights = 0; }
551         void enable_weights() { weights = new Weights(); }
552         void write_weights(const char *fn) { weights->write(fn); }
553         int forward(double start, double end, double &time) {
554                 //if( !weights->audio_forward(start, end, time) ) return 0;
555                 return weights->minimum_forward(start, end, time);
556         }
557         int backward(double start, double end, double &time) {
558                 //if( !weights->audio_backward(start, end, time) ) return 0;
559                 return weights->minimum_backward(start, end, time);
560         }
561
562         void init(zmpeg3_t *zsrc, int ztrk);
563         int load_samples(int ch);
564         int read_samples();
565         double weight();
566
567         Audio() { weights = 0; bfr = 0; }
568         ~Audio() { delete weights;  delete [] bfr; }
569 } audio;
570
571 void Audio::init(zmpeg3_t *zsrc, int ztrk)
572 {
573         src = zsrc;  trk = ztrk;
574         trks = src->total_astreams();
575         chans = src->audio_channels(trk);
576         rate = src->sample_rate(trk);
577         len = src->audio_samples(trk);
578         bfr = 0; bfr_sz = 0;
579         pos = 0;
580 }
581
582 int Audio::load_samples(int ch)
583 {
584         if( ch ) return src->reread_audio(bfr, ch, bfr_sz, trk);
585         if( src->read_audio(bfr, ch, bfr_sz, trk) ) fail();
586         pos += bfr_sz;
587         return 0;
588 }
589
590 int Audio::read_samples()
591 {
592         if( !weights ) return load_samples(0);
593         double wt = 0;
594         for( int ch=0; ch<chans; wt+=weight(), ++ch )
595                 if( load_samples(ch) ) fail();
596         weights->add(time(), wt/chans);
597         return 0;
598 }
599
600 double Audio::weight()
601 {
602         int64_t wt = 0;  short *bp = bfr;
603         int v = *bp++;
604         for( int i=bfr_sz; --i>0; v=*bp++ ) wt += abs(v-*bp);
605         return (double)wt / (bfr_sz-1);
606 }
607
608 class Scan
609 {
610         int clip_id;
611         int64_t creation_time, system_time;
612         int prefix_frames, suffix_frames, frames;
613         double prefix_length, suffix_length;
614         double reaction_time, margin;
615         double clip_start, clip_end, *clip_weights;
616         double start_time, end_time;
617         const char *asset_path;
618         static void set_priority(int pr, int io) {
619                 setpriority(PRIO_PROCESS, 0, pr);  // lowest cpu priority
620                 ioprio_set(IO_WHO_PROCESS, 0, IO_CLASS(io));
621         }
622         class low_priority { public:
623                 low_priority() { set_priority(19, IOPRIO_CLASS_IDLE); }
624                 ~low_priority() { set_priority(0, IOPRIO_CLASS_BE); }
625         };
626         class high_priority { public:
627                 high_priority() { set_priority(-4, IOPRIO_CLASS_BE); }
628                 ~high_priority() { set_priority(0, IOPRIO_CLASS_BE); }
629         };
630         int write_clip();
631 public:
632         int scan(Src &src, Deletions *dels);
633         int cut_clip(Src &src, Dele *del, Dele *next);
634         int add_clip();
635         int verify_clip(double position, double length);
636         int save_group(Margin *margin, double pos, int frame_no, int len, int group);
637
638         Scan();
639         ~Scan();
640 };
641
642 Scan::
643 Scan()
644 {
645         prefix_length = suffix_length = 2; // length of prefix/suffix in seconds
646         prefix_frames = video.frame_count(prefix_length);
647         suffix_frames = video.frame_count(suffix_length);
648         reaction_time = 1.0; // 1 sec reaction time for default start/end time
649         margin = 1.0; // 1 sec search window -1.0..+1.0 search margin
650         creation_time = system_time = 0;
651         clip_start = clip_end = 0;
652         clip_id = 0; clip_weights = 0;
653         db = new MediaDb();
654         db->openDb();
655         db->detachDb();
656 }
657
658 Scan::
659 ~Scan()
660 {
661         db->closeDb();
662         db = 0;
663 }
664
665 int Scan::
666 verify_clip(double position, double length)
667 {
668         Clips clips;
669         double start = position, end = position + length;
670         if( prefix->check(&clips, start, start+prefix_length, 1) ) fail();
671         if( !clips.count ) return 0;
672         if( suffix->check(&clips, end-suffix_length, end, 2) ) fail();
673         double avg_wt = video.average_weight(position, length);
674         for( Clip *clip=clips.first; clip; clip=clip->next ) {
675                 int clip_id = clip->clip_id;
676                 if( db->clip_id(clip_id) ) continue;
677                 double bias = avg_wt - db->clip_average_weight();
678                 if( fabs(bias) > 2*MEDIA_MEAN_ERRLMT ) continue;
679                 double iframerate = db->clip_framerate();
680                 if( iframerate <= 0. ) continue;
681                 int mframes = 2*check_margin * iframerate;
682                 int iframes = db->clip_frames();
683                 if( iframes < mframes ) continue;
684                 double ilength = iframes / iframerate;
685                 if( fabs(ilength-length) > 2*check_margin ) continue;
686                 double *iweights = db->clip_weights();
687                 double pos = position-check_margin;
688                 double kerr = MEDIA_WEIGHT_ERRLMT, kpos = -1;
689                 for( double dt=1/iframerate; --mframes>=0; pos+=dt ) {
690                         double err = video.err(pos, length, iframerate,
691                                                 iweights, iframes);
692 //printf(" clip %d, pos %f, err %f\n",clip_id,pos,err);
693                         if( err < kerr ) {
694                                 if( kpos >= 0 && err > kerr ) break;
695                                 kerr = err; kpos = pos;
696                         }
697                 }
698 //printf("vs clip %d, err %f, pos %f\n",clip_id,kerr,kpos);
699                 if( kpos >= 0 ) {
700                         printf(" dupl=%d, err=%f", clip_id, kerr);
701                         return -1;
702                 }
703         }
704         return 0;
705 }
706
707 int Scan::
708 save_group(Margin *margin, double pos, int frame_no, int len, int group)
709 {
710         if( margin->locate(pos) < 0 ) fail();
711         if( margin->length() < len ) fail();
712         while( --len >= 0 ) {
713                 uint8_t *sbfr = margin->get_frame();
714                 double offset = video.frame_position(frame_no++);
715                 double wt = video.frame_weight(margin->position());
716                 if( wt < lo_video || wt > hi_video ) continue;
717                 db->new_frame(clip_id, sbfr, frame_no, group, offset);
718 //write_pbm(sbfr,SWIDTH,SHEIGHT,"/tmp/dat/c%03df%05d.pbm",clip_id,frame_no);
719         }
720         return 0;
721 }
722
723
724
725 int read_to(double time)
726 {
727         int ret = 0;
728         while( !(ret=video.eof()) && video.time() < time ) video.read_frame();
729         while( !(ret=audio.eof()) && audio.time() < time ) audio.read_samples();
730         return ret;
731 }
732
733
734 int Scan::
735 scan(Src &src, Deletions *dels)
736 {
737         Dele *next = dels->first;
738         if( !next || !next->next ) return 0;
739         if( next->action != DEL_START ) fail();
740         next->action = DEL_MARK;
741         if( next->next->action == DEL_OOPS )
742                 next->next->action = DEL_SKIP;
743
744         time_t ct;  time(&ct);
745         creation_time = (int64_t)ct;
746         prefix = suffix = 0;
747         Dele *del = 0;
748
749         while( next ) {
750                 switch( next->action ) {
751                 case DEL_MARK:
752                         if( !cut_clip(src, del, next) ) add_clip();
753                         del = next;
754                         break;
755                 case DEL_SKIP:
756                         del = 0;
757                         break;
758                 default:
759                         fail();
760                 }
761                 do {
762                         Dele *nxt = next->next;
763                         while( nxt && nxt->next && nxt->next->action == DEL_OOPS ) {
764                                 while( (nxt=nxt->next) && nxt->action == DEL_OOPS );
765                         }
766                         next = nxt;
767                 } while( next && del && (next->time-del->time) < 3 );
768                 delete prefix;  prefix = suffix;  suffix = 0;
769         }
770
771         delete prefix;  prefix = 0;
772         return 0;
773 }
774
775
776 int Scan::
777 cut_clip(Src &src, Dele *del, Dele *next)
778 {
779         low_priority set_lo;
780         double next_suffix_length = !del ? 0 : suffix_length;
781         double next_prefix_length = !next->next ? 0 : prefix_length;
782         double suffix_start_time =
783                 next->time - prefix_length - next_suffix_length - margin;
784         if( read_to(suffix_start_time) ) fail();
785         // create new suffix, copy prefix if overlap
786         suffix = new Margin(video.frame_no(), video.time());
787         if( prefix && prefix->locate(suffix_start_time) >= 0 )
788                 suffix->copy(prefix);
789         // read_to next mark, get system time
790         video.margin_frames(suffix);
791         if( read_to(next->time) ) fail();
792         system_time = src->dvb.get_system_time();
793         // read_to end of next prefix
794         double prefix_end_time = next->time + next_prefix_length + margin;
795         if( read_to(prefix_end_time) ) fail();
796         video.margin_frames();
797         // if no previous mark, return
798         if( !del ) return -1;
799         asset_path = src.asset_path();
800         double cut_start = del->time, cut_end = next->time;
801         if( cut_end-cut_start < min_clip_time ) return 1;
802         // default start/end clip endpoints
803         start_time = cut_start - reaction_time;
804         end_time = cut_end - reaction_time;
805         // find edges, if possible.  default to reaction time
806         audio.backward(cut_start-prefix_length, cut_start, start_time);
807         video.forward(start_time-margin,start_time+margin, start_time);
808         audio.forward(cut_end-suffix_length, cut_end, end_time);
809         video.backward(end_time-margin,end_time+margin, end_time);
810         // validate length
811         frames = video.frame_count(end_time - start_time);
812         if( frames < prefix_frames + suffix_frames ) fail();
813         // report ranges
814         clip_start = start_time - video.origin();
815         clip_end = end_time - video.origin();
816         printf(" %f-%f (%f), %f-%f %jd-%jd",
817                 start_time, end_time, end_time-start_time, clip_start, clip_end,
818                 video.frame_count(clip_start), video.frame_count(clip_end));
819         return 0;
820 }
821
822 int Scan::
823 add_clip()
824 {
825         high_priority set_hi;
826         if( !db || !db->is_open() ) {
827                 fprintf(stderr,"unable to open db: " MEDIA_DB "\n");
828                 fail();
829         }
830         db->attachDb(1);
831         // check for duplicate
832         if( verify_clip(start_time, end_time-start_time) ) {
833         }
834         else if( !write_clip() ) {
835                 printf(" new %d",clip_id);
836                 db->commitDb();
837         }
838         else {
839                 printf(" failed");
840                 db->undoDb();
841         }
842         db->detachDb();
843         printf("\n"); fflush(stdout);
844         return 0;
845 }
846
847 int Scan::
848 write_clip()
849 {
850         if( db->new_clip_set(video.title(), asset_path, clip_start,
851                 video.framerate(), frames, prefix_frames, suffix_frames,
852                 creation_time, system_time) ) fail();
853         // save prefix video group
854         int frame_no = 0;
855         clip_id = db->clip_id();
856         if( save_group(prefix, start_time, frame_no, prefix_frames, 1) ) fail();
857         // save suffix video group
858         double start_suffix = end_time - suffix_length;
859         frame_no = frames - suffix_frames;
860         if( save_group(suffix, start_suffix, frame_no, suffix_frames, 2) ) fail();
861         // save video weights
862         if( video.save_weights(start_time, db->clip_weights(), frames) ) fail();
863         double avg_wt = video.average_weight(start_time, end_time-start_time);
864         db->clip_average_weight(avg_wt);
865         return 0;
866 }
867
868 void sig_segv(int n)
869 {
870         printf("\nsegv, pid=%d\n",getpid()); fflush(stdout);
871         sleep(1000000);
872 }
873
874 void sig_hup(int n)
875 {
876 }
877
878 int main(int ac, char **av)
879 {
880 printf("started %d\n",getpid());
881         signal(SIGSEGV,sig_segv);
882         signal(SIGHUP,sig_hup);
883
884         if( ac < 2 ) {
885                 fprintf(stderr, "usage: %s <xml.del>\n", av[1]);
886                 fail();
887         }
888         // read in deletions
889         Deletions *deletions = Deletions::read_dels(av[1]);
890         if( !deletions ) {
891                 fprintf(stderr, "read_dels: %s failed\n", av[1]);
892                 fail();
893         }
894
895         // open mpeg3 dvb media
896         Src src(deletions->file_path());
897         if( !src.open() ) {
898                 fprintf(stderr, "open: %s failed\n", deletions->file_path());
899                 fail();
900         }
901         src->set_cpus(4);
902         src->set_pts_padding(-1);
903
904         // find video stream
905         int vtrk = src->total_vstreams();
906         while( --vtrk >= 0 && src->video_pid(vtrk) != deletions->pid );
907         if( vtrk < 0 ) {
908                 fprintf(stderr, "pid %d failed\n", deletions->pid);
909                 fail();
910         }
911         video.init(src, vtrk);
912         if( video.framerate() <= 0 ) {
913                 fprintf(stderr, "framerate %d failed\n", vtrk);
914                 fail();
915         }
916
917         // find first audio stream associate to video stream
918         int n, atrk = -1;
919         int elements = src->dvb.channel_count();
920         for( n=0; atrk<0 && n<elements; ++n ) {
921                 int atracks, vtracks;
922                 if( src->dvb.total_vstreams(n,vtracks) ) continue;
923                 for( int i=0; atrk<0 && i<vtracks; ++i ) {
924                         if( src->dvb.vstream_number(n,i,vtrk) ) continue;
925                         if( vtrk != video.track() ) continue;
926                         if( src->dvb.total_astreams(n,atracks) ) continue;
927                         if( !atracks ) continue;
928                         if( src->dvb.astream_number(n,0,atrk,0) ) continue;
929                 }
930         }
931         int major, minor;  char title[64];
932         if( !src->dvb.get_channel(n, major, minor) ) {
933                 sprintf(title, "%3d.%-3d", major, minor);
934                 video.set_title(title);
935         }
936
937         if( atrk < 0 ) {
938                 fprintf(stderr, "audio %d failed\n", video.track());
939                 fail();
940         }
941         audio.init(src, atrk);
942         audio.set_buffer(audio.samplerate()/video.framerate()+64);
943
944         // read to the first valid frame
945         if( read_to(0) ) fail();
946         double last_time = video.time(), origin = -1;
947         while( !video.eof() ) {
948                 if( video.frame_position(video.frame_no()) >= 5 ) break;
949                 if( video.raw_weight() >= video_cutoff ) { origin = last_time; break; }
950                 last_time = video.time();
951                 if( video.read_frame() ) break;
952         }
953         if( origin < 0 ) {
954                 fprintf(stderr, "origin %d failed\n", video.track());
955                 fail();
956         }
957         video.set_origin(origin);
958
959         double audio_time = audio.time(), video_time = video.time();
960         double start_time = audio_time > video_time ? audio_time : video_time;
961         if( read_to(start_time) ) fail();
962
963         audio.enable_weights();
964         video.enable_weights();
965
966         Scan s;
967         s.scan(src, deletions);
968
969 //audio.write_weights("/tmp/audio.wts");
970 //video.write_weights("/tmp/video.wts");
971         audio.disable_weights();
972         video.disable_weights();
973
974 //      ::remove(deletions->path());
975 //      ::remove(av[1]);
976
977         delete deletions;
978 printf("completed %d\n",getpid());
979 //sleep(1000000);
980         return 0;
981 }
982