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