workaround for ub16 compiler problem
[goodguy/cinelerra.git] / cinelerra-5.1 / cinelerra / mediadb.C
1 #include <stdio.h>
2 #include <stdarg.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <limits.h>
7 #include <time.h>
8
9 #include "../db/tdb.h"
10 #include "../db/s.C"
11
12 #include "linklist.h"
13 #include "mediadb.h"
14 #include "filexml.h"
15
16
17
18 void write_pgm(uint8_t *tp, int w, int h, const char *fmt, ...)
19 {
20   va_list ap;    va_start(ap, fmt);
21   char fn[256];  vsnprintf(fn, sizeof(fn), fmt, ap);
22   va_end(ap);
23   FILE *fp = !strcmp(fn,"-") ? stdout : fopen(fn,"w");
24   if( fp ) {
25     fprintf(fp,"P5\n%d %d\n255\n",w,h);
26     fwrite(tp,w,h,fp);
27     fclose(fp);
28   }
29 }
30
31 void write_ppm(uint8_t *tp, int w, int h, const char *fmt, ...)
32 {
33   va_list ap;    va_start(ap, fmt);
34   char fn[256];  vsnprintf(fn, sizeof(fn), fmt, ap);
35   va_end(ap);
36   FILE *fp = !strcmp(fn,"-") ? stdout : fopen(fn,"w");
37   if( fp ) {
38     fprintf(fp,"P6\n%d %d\n255\n",w,h);
39     fwrite(tp,3*w,h,fp);
40     fclose(fp);
41   }
42 }
43
44 static inline int clip(int v, int mn, int mx)
45 {
46   return v<mn ? mn : v>mx ? mx : v;
47 }
48
49 void pbm_diff(uint8_t *ap, uint8_t *bp, int w, int h, const char *fmt, ...)
50 {
51   va_list va;    va_start(va, fmt);
52   char fn[256];  vsnprintf(fn, sizeof(fn), fmt, va);
53   va_end(va);
54   FILE *fp = !strcmp(fn,"-") ? stdout : fopen(fn,"w");
55   if( fp ) {
56     int sz = w*h;
57     uint8_t dat[sz], *dp = dat;
58     for( int i=sz; --i>=0; ++dp,++ap,++bp ) *dp = clip(*ap-*bp+128, 0 ,255);
59     fprintf(fp,"P5\n%d %d\n255\n",w,h);
60     fwrite(dat,w,h,fp);
61     fclose(fp);
62   }
63 }
64
65
66 void Scale::
67 scale(uint8_t *odata, int ow, int oh, int sx, int sy, int sw, int sh)
68 {
69   pw = (double)(ow * oh) / (rw * rh);
70   odat = odata + sy*ow + sx;
71   iy1 = 0;  yf1 = 0;
72   double r = 0.5;
73
74   for( int dy=1; dy<=sh; ++dy, odat+=ow ) {
75     iy0 = iy1;  yf0 = 1.0 - yf1;
76     double ny = (double)(dy * rh) / oh;
77     iy1 = (int) ny;  yf1 = ny-iy1;
78     uint8_t *bp = odat;
79     ix1 = 0;  xf1 = 0;
80
81     for( int dx=1; dx<=sw; ++dx ) {
82       ix0 = ix1;  xf0 = 1.0 - xf1;
83       double nx = (double)(dx*rw) / ow;
84       ix1 = (int)nx;  xf1 = nx-ix1;
85       double px = scalexy();
86       int ipx = px + r;
87       r += px - ipx;
88       *bp++ = ipx;
89     }
90   }
91 }
92
93
94 Deletions::
95 Deletions(int pid, const char *fn)
96 {
97         this->pid = pid;
98         strcpy(this->filepath,fn);
99 }
100
101 Deletions::
102 ~Deletions()
103 {
104 }
105
106
107 int Deletions::
108 load(FileXML &xml)
109 {
110         for(;;) {
111                 if( xml.read_tag() != 0 ) return 1;
112                 if( !xml.tag.title_is("DEL") ) break;
113                 double time = xml.tag.get_property("TIME", (double)0.0);
114                 int action = xml.tag.get_property("ACTION", (int)0);
115                 append(new Dele(time, action));
116         }
117         return 0;
118 }
119
120 Deletions *Deletions::
121 read_dels(const char *filename)
122 {
123         FileXML xml;
124         if( xml.read_from_file(filename, 1) ) return 0;
125         do {
126                 if( xml.read_tag() ) return 0;
127         } while( !xml.tag.title_is("DELS") );
128
129         int pid = xml.tag.get_property("PID", (int)-1);
130         const char *file = xml.tag.get_property("FILE");
131         Deletions *dels = new Deletions(pid, file);
132         if( dels->load(xml) || !xml.tag.title_is("/DELS") ) {
133                  delete dels; dels = 0;
134         }
135         return dels;
136 }
137
138 int Deletions::
139 save(FileXML &xml)
140 {
141         xml.tag.set_title("DELS");
142         xml.tag.set_property("PID", pid);
143         xml.tag.set_property("FILE", filepath);
144         xml.append_tag();
145         xml.append_newline();
146
147         for( Dele *del=first; del; del=del->next ) {
148                 xml.tag.set_title("DEL");
149                 xml.tag.set_property("TIME", del->time);
150                 xml.tag.set_property("ACTION", del->action);
151                 xml.append_tag();
152                 xml.tag.set_title("/DEL");
153                 xml.append_tag();
154                 xml.append_newline();
155         }
156
157         xml.tag.set_title("/DELS");
158         xml.append_tag();
159         xml.append_newline();
160         return 0;
161 }
162
163 int Deletions::
164 write_dels(const char *filename)
165 {
166         FILE *fp = fopen(filename, "wb");
167         if( !fp != 0 ) return 1;
168         FileXML xml;
169         save(xml);
170         xml.terminate_string();
171         xml.write_to_file(fp);
172         fclose(fp);
173         return 0;
174 }
175
176
177 Clip::
178 Clip(int clip_id, double start, double end, int index)
179 {
180         this->clip_id = clip_id;
181         this->start = start;
182         this->end = end;
183         this->index = index;
184         this->votes = 0;
185         this->groups = 0;
186         this->cycle = 0;
187         this->muted = 0;
188         this->best = 1.e99;
189 }
190
191 Clip::
192 ~Clip()
193 {
194         Clips *clips = (Clips *)list;
195         if( clips && clips->current == this ) clips->current = 0;
196 }
197
198 Clip *Clips::locate(int id, double start, double end)
199 {
200         if( current && current->clip_id == id &&
201             end >= current->start && start < current->end )
202                 return current;
203         for( current=first; current!=0; current=current->next ) {
204                 if( current->clip_id != id ) continue;
205                 if( current->start <= end && current->end > start ) break;
206         }
207         return current;
208 }
209
210 Clips::
211 Clips(int pid)
212 {
213         this->pid = pid;
214         this->current = 0;
215         this->count = 0;
216         this->cycle = 0;
217 }
218
219 Clips::
220 ~Clips()
221 {
222 }
223
224 Clip *Clips::
225 new_clip(int clip_id, double start, double end, int index)
226 {
227         return append(new Clip(clip_id, start, end, index));
228 }
229
230 int Clips::
231 get_clip(int idx, int &id, double &start, double &end)
232 {
233         int votes = -1, cid = -1;
234         double st = 0, et = 0;
235
236         for( Clip *clip=first; clip!=0; clip=clip->next ) {
237                 if( clip->index != idx ) continue;
238                 // must have prefix+suffix frames
239                 if( clip->groups != 3 ) continue;
240                 if( clip->votes < votes ) continue;
241                 if( clip->votes > votes ) {
242                         // first/better match
243                         votes = clip->votes;
244                         cid = clip->clip_id;
245                         st = clip->start;
246                         et = clip->end;
247                         continue;
248                 }
249                 // reject if not overlapping
250                 if( clip->start > et ) continue;
251                 if( clip->end < st ) continue;
252                 // expand range on overlap
253                 if( clip->start < st ) st = clip->start;
254                 if( clip->end > et ) et = clip->end;
255         }
256
257         if( votes > 2 ) {
258                 start = st;  end = et;  id = cid;
259                 return 1;
260         }
261
262         return 0;
263 }
264
265 int Clips::
266 check(int clip_id, double start, double end, int group, double err)
267 {
268         Clip *clip = locate(clip_id, start, end);
269         if( clip ) {
270                 if( err < clip->best ) {
271                         clip->best = err;
272                         if( start < clip->start ) clip->start = start;
273                         if( end > clip->end ) clip->end = end;
274                 }
275         }
276         else
277                 clip = new_clip(clip_id, start, end, count++);
278         clip->groups |= group;
279         if( cycle != clip->cycle ) {
280                  clip->cycle = cycle;
281                 ++clip->votes;
282 //printf("clip %d votes %d\n",clip_id,clip->votes);
283         }
284         return 0;
285 }
286
287 int Clips::
288 save(FileXML &xml)
289 {
290         xml.tag.set_title("CLIPS");
291         xml.tag.set_property("PID", pid);
292         xml.append_tag();
293         xml.append_newline();
294
295         for( int idx=0; idx<count; ++idx ) {
296                 int id;  double start, end;
297                 if( !get_clip(idx, id, start, end) ) continue;
298                 xml.tag.set_title("CLIP");
299                 xml.tag.set_property("ID", id);
300                 xml.tag.set_property("START", start);
301                 xml.tag.set_property("END", end);
302                 xml.append_tag();
303                 xml.tag.set_title("/CLIP");
304                 xml.append_tag();
305                 xml.append_newline();
306         }
307
308         xml.tag.set_title("/CLIPS");
309         xml.append_tag();
310         xml.append_newline();
311         return 0;
312 }
313
314 int Clips::
315 load(FileXML &xml)
316 {
317         int i = 0;
318         for(;;) {
319                 if( xml.read_tag() != 0 ) return 1;
320                 if( !xml.tag.title_is("CLIP") ) break;
321                 int clip_id = xml.tag.get_property("ID", (int)0);
322                 double start = xml.tag.get_property("START", (double)0.0);
323                 double end = xml.tag.get_property("END", (double)0.0);
324                 append(new Clip(clip_id, start, end, i++));
325         }
326         count = i;
327         return 0;
328 }
329
330
331 Snip::
332 Snip(int clip_id, double start, double end, int index)
333  : Clip(clip_id, start, end, index)
334 {
335 }
336
337 Snip::
338 ~Snip()
339 {
340 }
341
342 Snips::
343 Snips()
344 {
345 }
346
347 Snips::
348 ~Snips()
349 {
350 }
351
352 Clip *Snips::
353 new_clip(int clip_id, double start, double end, int index)
354 {
355         return append(new Snip(clip_id, start, end, index));
356 }
357
358
359 MediaDb::
360 MediaDb()
361 {
362         opened = 0;
363         db = new theDb();
364 }
365
366 MediaDb::
367 ~MediaDb()
368 {
369         if( opened ) closeDb();
370         delete db;
371 }
372
373 int MediaDb::is_open() { return opened > 0 ? opened : 0; }
374 int MediaDb::transaction() { return db->transaction(); }
375
376 // clip_set accessors
377 int MediaDb::clip_id(int id) { return db->clip_set.FindId(id); }
378 int MediaDb::clip_id() { return db->clip_set.id(); }
379 int MediaDb::clips_first_id() { return db->clip_set.FirstId(clip_id_loc); }
380 int MediaDb::clips_next_id() { return db->clip_set.NextId(clip_id_loc); }
381 const char *MediaDb::clip_title() { return db->clip_set.Title(); }
382 const char *MediaDb::clip_path() { return db->clip_set.Asset_path(); }
383 double MediaDb::clip_position() { return db->clip_set.Position(); }
384 double MediaDb::clip_framerate() { return db->clip_set.Framerate(); }
385 double MediaDb::clip_average_weight() { return db->clip_set.Average_weight(); }
386 void MediaDb::clip_average_weight(double avg_wt) { return db->clip_set.Average_weight(avg_wt); }
387 int MediaDb::clip_frames() { return db->clip_set.Frames(); }
388 double MediaDb::clip_length() {
389         return clip_framerate()>0 ? clip_frames() / clip_framerate() : 0;
390 }
391 int MediaDb::clip_prefix_size() { return db->clip_set.Prefix_size(); }
392 int MediaDb::clip_suffix_size() { return db->clip_set.Suffix_size(); }
393 int MediaDb::clip_size() { return clip_prefix_size()+clip_suffix_size(); }
394 double *MediaDb::clip_weights() { return (double *)db->clip_set._Weights(); }
395 int64_t MediaDb::clip_creation_time() { return db->clip_set.Creation_time(); }
396 int64_t MediaDb::clip_system_time() { return db->clip_set.System_time(); }
397
398 // MediaDb::timeline accessors
399 int MediaDb::timeline_id(int id) { return db->timeline.FindId(id); }
400 int MediaDb::timeline_id() { return db->timeline.id(); }
401 int MediaDb::timeline_clip_id() { return db->timeline.Clip_id(); }
402 int MediaDb::timeline_sequence_no() { return db->timeline.Sequence_no(); }
403 int MediaDb::timeline_frame_id() { return db->timeline.Frame_id(); }
404 int MediaDb::timeline_group() { return db->timeline.Group(); }
405 double MediaDb::timeline_offset() { return db->timeline.Time_offset(); }
406
407 // MediaDb::clip_view accessors
408 int MediaDb::views_id(int id) { return db->clip_views.FindId(id); }
409 int MediaDb::views_id() { return db->clip_views.id(); }
410 int MediaDb::views_clip_id(int id) {
411         return Clip_viewsLoc::ikey_Clip_access(db->clip_views,id).Find();
412 }
413 int MediaDb::views_clip_id() { return db->clip_views.Access_clip_id(); }
414 int64_t MediaDb::views_access_time() { return db->clip_views.Access_time(); }
415 int MediaDb::views_access_count() { return db->clip_views.Access_count(); }
416
417
418 double MediaDb::
419 mean(uint8_t *dat, int n, int stride)
420 {
421   int s = 0;
422   for( int i=0; i<n; ++i, dat+=stride ) s += *dat;
423   return (double)s / n;
424 }
425
426 double MediaDb::
427 variance(uint8_t *dat, double m, int n, int stride)
428 {
429   double ss = 0, s = 0;
430   for( int i=0; i<n; ++i, dat+=stride ) {
431     double dx = *dat-m;
432     s += dx;  ss += dx*dx;
433   }
434   return (ss - s*s / n) / (n-1);
435 }
436
437 double MediaDb::
438 centroid(uint8_t *dat, int n, int stride)
439 {
440   int s = 0, ss = 0;
441   for( int i=0; i<n; dat+=stride ) { s += *dat;  ss += ++i * *dat; }
442   return s > 0 ? (double)ss / s : n / 2.;
443 }
444
445 void MediaDb::
446 centroid(uint8_t *dat, int w, int h, double &xx, double &yy)
447 {
448         double x = 0, y = 0;
449         uint8_t *dp = dat;
450         for( int i=h; --i>=0; dp+=w ) x += centroid(dp, w);
451         for( int i=w; --i>=0; ) y += centroid(dat+i, h, w);
452         xx = x / h;  yy = y / w;
453 }
454
455 void MediaDb::
456 deviation(uint8_t *a, uint8_t *b, int sz, int margin,
457                 double &dev, int &ofs, int stride)
458 {
459   double best = 1e100;  int ii = -1;
460   for( int i=-margin; i<=margin; ++i ) {
461     int aofs = i < 0 ? 0 : i*stride;
462     int bofs = i < 0 ? -i*stride : 0;
463     uint8_t *ap = a + aofs, *bp = b + bofs;
464     int ierr = 0; int n = sz - abs(i);
465     for( int j=n; --j>=0; ap+=stride,bp+=stride ) ierr += abs(*ap - *bp);
466     double err = (double)ierr / n;
467     if( err < best ) { best = err;  ii = i; }
468   }
469   dev = best;
470   ofs = ii;
471 }
472
473 int64_t MediaDb::
474 cmpr(uint8_t *ap, uint8_t *bp, int sz)
475 {
476   int64_t v = 0;
477   for( int i=sz; --i>=0; ++ap,++bp ) v += abs(*ap-*bp);
478   return v;
479 }
480
481 int64_t MediaDb::
482 diff(uint8_t *a, uint8_t *b, int w, int h, int dx, int dy, int bias)
483 {
484   int axofs = dx < 0 ? 0 : dx;
485   int ayofs = w * (dy < 0 ? 0 : dy);
486   int aofs = ayofs + axofs;
487   int bxofs = dx < 0 ? -dx : 0;
488   int byofs = w * (dy < 0 ? -dy : 0);
489   int bofs = byofs + bxofs;
490   uint8_t *ap = a + aofs, *bp = b + bofs;
491   int ierr = 0, ww = w-abs(dx), hh = h-abs(dy);
492   for( int y=hh; --y>=0; ap+=w, bp+=w ) {
493     a = ap;  b = bp;
494     for( int x=ww; --x>=0; ++a, ++b ) { ierr += abs(*a-bias - *b); }
495   }
496   return ierr;
497 }
498
499 void MediaDb::
500 fit(int *dat, int n, double &a, double &b)
501 {
502         double st2 = 0, sb = 0;
503         int64_t sy = 0;
504         for( int i=0; i<n; ++i ) sy += dat[i];
505         double mx = (n-1)/2., my = (double)sy / n;
506         for( int i=0; i<n; ++i ) {
507                 double t = i - mx;
508                 st2 += t * t;
509                 sb += t * dat[i];
510         }
511         b = sb / st2;
512         a = my - mx*b;
513 }
514
515 void MediaDb::
516 eq(uint8_t *sp, uint8_t *dp, int len)
517 {
518 #if 0
519         int hist[256], wts[256], map[256];
520         for( int i=0; i<256; ++i ) hist[i] = 0;
521         uint8_t *bp = sp;
522         for( int i=len; --i>=0; ++bp ) ++hist[*bp];
523         int t = 0;
524         for( int i=0; i<256; ++i ) { t += hist[i];  wts[i] = t; }
525         int lmn = len/20, lmx = len-lmn;
526         int mn =   0;  while( mn < 256 && wts[mn] < lmn ) ++mn;
527         int mx = 255;  while( mx > mn && wts[mx] > lmx ) --mx;
528         double a, b;
529         fit(&wts[mn], mx-mn, a, b);
530         double r = 256./len;
531         double a1 = (a - b*mn) * r, b1 = b * r;
532         for( int i=0 ; i<256;  ++i ) map[i] = clip(a1 + i*b1, 0, 255);
533         for( int i=len; --i>=0; ++sp,++dp ) *dp = map[*sp];
534 #else
535         if( sp != dp ) memcpy(dp,sp,len);
536 #endif
537 }
538
539 // record constructors
540 int MediaDb::
541 new_frame(int clip_id, uint8_t *dat, int no, int group, double offset)
542 {
543 //printf("add %d at %f\n",no,tm);
544         int fid, ret = get_media_key(dat, fid, MEDIA_FRAME_DIST, MEDIA_FRAME_ERRLMT);
545         if( ret < 0 ) return -1;
546         if( ret > 0 ) {
547                 db->video_frame.Allocate();
548                 uint8_t *fp = db->video_frame._Frame_data(SFRM_SZ);
549                 eq(dat, fp, SFRM_SZ);
550                 double mn = mean(dat, SFRM_SZ);
551                 db->video_frame.Frame_mean(mn);
552                 double sd = std_dev(dat, mn, SFRM_SZ);
553                 db->video_frame.Frame_std_dev(sd);
554                 double cx, cy;  centroid(dat, SWIDTH, SHEIGHT, cx, cy);
555                 db->video_frame.Frame_cx(cx);
556                 db->video_frame.Frame_cy(cy);
557                 double moment = cx + cy;
558                 db->video_frame.Frame_moment(moment);
559                 db->video_frame.Construct();
560                 fid = db->video_frame.id();
561         }
562         db->timeline.Allocate();
563         db->timeline.Clip_id(clip_id);
564         db->timeline.Sequence_no(no);
565         db->timeline.Frame_id(fid);
566         db->timeline.Group(group);
567         db->timeline.Time_offset(offset);
568         db->timeline.Construct();
569         return 0;
570 }
571
572 int MediaDb::
573 new_clip_set(const char *title, const char *asset_path, double position,
574         double framerate, int frames, int prefix_size, int suffix_size,
575         int64_t creation_time, int64_t system_time)
576 {
577         db->clip_set.Allocate();
578         db->clip_set.Title(title);
579         db->clip_set.Asset_path(asset_path);
580         db->clip_set.Position(position);
581         db->clip_set.Framerate(framerate);
582         db->clip_set.Frames(frames);
583         db->clip_set.Prefix_size(prefix_size);
584         db->clip_set.Suffix_size(suffix_size);
585         db->clip_set._Weights(frames*sizeof(double));
586         db->clip_set.Creation_time(creation_time);
587         db->clip_set.System_time(system_time);
588         db->clip_set.Construct();
589
590         return new_clip_view(db->clip_set.id(), creation_time);
591 }
592
593 int MediaDb::
594 new_clip_view(int clip_id, int64_t access_time)
595 {
596         db->clip_views.Allocate();
597         db->clip_views.Access_clip_id(clip_id);
598         db->clip_views.Access_time(access_time);
599         db->clip_views.Access_count(1);
600         db->clip_views.Construct();
601         return 0;
602 }
603
604 int MediaDb::
605 access_clip(int clip_id, int64_t access_time)
606 {
607         if( Clip_viewsLoc::ikey_Clip_access(db->clip_views,clip_id).Find() ) return 1;
608         db->clip_views.Destruct();
609         if( !access_time ) { time_t at;  ::time(&at);  access_time = (int64_t)at; }
610         db->clip_views.Access_time(access_time);
611         int access_count = db->clip_views.Access_count();
612         db->clip_views.Access_count(access_count+1);
613         db->clip_views.Construct();
614         return 0;
615 }
616
617 int MediaDb::
618 del_clip_set(int clip_id)
619 {
620         if( db->clip_set.FindId(clip_id) ) return 1;
621         // delete clip_set
622         db->clip_set.Destruct();
623         db->clip_set.Deallocate();
624         // delete clip_views
625         if( Clip_viewsLoc::ikey_Clip_access(db->clip_views,clip_id).Find() ) return 1;
626         db->clip_views.Destruct();
627         db->clip_views.Deallocate();
628         // delete timeline
629         while( !TimelineLoc::ikey_Sequences(db->timeline,clip_id,0).Locate() ) {
630                 if( clip_id != (int)db->timeline.Clip_id() ) break;
631                 int frame_id = db->timeline.Frame_id();
632                 db->timeline.Destruct();
633                 db->timeline.Deallocate();
634                 // check for more timeline refs
635                 if( !TimelineLoc::ikey_Timelines(db->timeline, frame_id).Locate() &&
636                         frame_id == (int)db->timeline.Frame_id() ) continue;
637                 if( db->video_frame.FindId(frame_id) ) continue;
638                 // delete frame
639                 db->video_frame.Destruct();
640                 db->video_frame.Deallocate();
641         }
642         return 0;
643 }
644
645
646 // db file operations
647 int MediaDb::
648 newDb()
649 {
650         if( opened > 0 || db->create(MEDIA_DB) ) {
651                 printf("MediaDb::newDb failed\n");
652                 return opened = -1;
653         }
654         return opened = 0;
655 }
656
657 int MediaDb::
658 openDb(int rw)
659 {
660         if( opened > 0 ) return 0;
661         if( db->access(MEDIA_DB, MEDIA_SHM_KEY, rw) ) {
662                 printf("MediaDb::openDb failed\n");
663                 return opened = -1;
664         }
665         opened = 1;
666         return 0;
667 }
668
669 int MediaDb::
670 resetDb(int rw)
671 {
672         int result = 0;
673         if( access(MEDIA_DB, F_OK) ) result = newDb();
674         if( !result ) result = openDb(rw);
675         return result;
676 }
677
678 void MediaDb::
679 closeDb()
680 {
681         if( opened > 0 ) { detachDb();  db->close(); }
682         opened = 0;
683 }
684
685 void MediaDb::
686 commitDb()
687 {
688         if( opened > 0 ) db->commit();
689 }
690
691 void MediaDb::
692 undoDb()
693 {
694         if( opened > 0 ) db->undo();
695 }
696
697 int MediaDb::
698 attachDb(int rw)
699 {
700         return opened > 0 ? db->attach(rw) : -1;
701 }
702
703 int MediaDb::
704 detachDb()
705 {
706         return opened > 0 ? db->detach() : -1;
707 }
708
709
710 int MediaDb::
711 get_timelines(int frame_id)
712 {
713         return TimelineLoc::ikey_Timelines(db->timeline, frame_id).Locate();
714 }
715
716 int MediaDb::
717 next_timelines()
718 {
719         return TimelineLoc::rkey_Timelines(db->timeline).Next();
720 }
721
722 int MediaDb::
723 get_sequences(int clip_id, int seq_no)
724 {
725         int ret = TimelineLoc::ikey_Sequences(db->timeline,clip_id,seq_no).Locate();
726         if( !ret && clip_id != (int)db->timeline.Clip_id() ) ret = 1;
727         return ret;
728 }
729
730 int MediaDb::
731 next_sequences()
732 {
733         return TimelineLoc::rkey_Sequences(db->timeline).Next();
734 }
735
736 // frame accessors
737 int MediaDb::
738 get_image(int id, uint8_t *dat, int &w, int &h)
739 {
740         int result = 1;
741         if( !db->video_frame.FindId(id) ) {
742                 memcpy(dat, db->video_frame.Frame_data(), SFRM_SZ);
743                 w = SWIDTH;  h = SHEIGHT;
744                 result = 0;
745         }
746         return result;
747 }
748
749 int MediaDb::
750 get_frame_key(uint8_t *dat, int &fid,
751                 Clips *clips, double pos, int mask)
752 {
753         return get_media_key(dat, fid,
754                         MEDIA_SEARCH_DIST, MEDIA_SEARCH_ERRLMT,
755                         clips, pos, mask);
756 }
757
758 int MediaDb::
759 get_media_key(uint8_t *dat, int &fid, int dist, double errlmt,
760                 Clips *clips, double pos, int mask)
761 {
762         fmean = mean(dat, SFRM_SZ);
763         if( fmean < 17 ) return -1; // black, forbidden
764         if( fmean > 239 ) return -1; // white, forbidden
765         distance = dist;
766         pixlmt = SFRM_SZ*errlmt;
767         position = pos;
768         group_mask = mask;
769         fsd = std_dev(dat, fmean, SFRM_SZ);
770         centroid(dat, SWIDTH, SHEIGHT, fcx, fcy);
771         fmoment = fcx + fcy;
772         eq(dat, frm, SFRM_SZ);
773         if( clips ) ++clips->cycle;
774         lid = nid = -1;
775         lerr = LONG_MAX;
776         int result = 1;
777
778         if( !Video_frameLoc::ikey_Frame_center(db->video_frame, fmoment).Locate() ) {
779                 Video_frameLoc prev(db->video_frame), next(db->video_frame);
780                 Video_frameLoc::rkey_Frame_center prev_ctr(prev), next_ctr(next);
781                 if( !get_key(clips, prev_ctr, next_ctr) ) result = 0;
782         }
783         if( !Video_frameLoc::ikey_Frame_weight(db->video_frame, fmean).Locate() ) {
784                 Video_frameLoc prev(db->video_frame), next(db->video_frame);
785                 Video_frameLoc::rkey_Frame_weight prev_wt(prev), next_wt(next);
786                 if( !get_key(clips, prev_wt, next_wt) ) result = 0;
787         }
788
789         fid = lid;
790         return result;
791 }
792
793 int MediaDb::
794 get_key(Clips *clips, Db::rKey &prev_rkey, Db::rKey &next_rkey)
795 {
796         key_compare(clips, next_rkey.loc);
797         // search outward from search target for frame with least error
798         Db::pgRef next_loc;  next_rkey.NextLoc(next_loc);
799
800         for( int i=1; i<=distance; ++i ) {
801                 if( !prev_rkey.Locate(Db::keyLT) )
802                         key_compare(clips,prev_rkey.loc);
803                 if( !next_rkey.Next(next_loc) )
804                         key_compare(clips,next_rkey.loc);
805         }
806
807         return lerr < pixlmt ? 0 : 1;
808 }
809
810 int64_t MediaDb::
811 key_compare(Clips *clips, Db::ObjectLoc &loc)
812 {
813         int id = loc.id();
814         if( nid == id ) return 1;
815         nid = id;
816         Video_frameLoc &frame = *(Video_frameLoc*)&loc;
817         double dm = frame.Frame_mean()-fmean;
818         if( fabs(dm) > MEDIA_MEAN_ERRLMT ) return 1;
819         double ds = frame.Frame_std_dev()-fsd;
820         if( fabs(ds) > MEDIA_STDDEV_ERRLMT ) return 1;
821         double dx = frame.Frame_cx()-fcx;
822         if( fabs(dx) > MEDIA_XCENTER_ERRLMT ) return 1;
823         double dy = frame.Frame_cy()-fcy;
824         if( fabs(dy) > MEDIA_YCENTER_ERRLMT ) return 1;
825         uint8_t *dat = frame._Frame_data();
826         //int64_t err = cmpr(frm, dat, SFRM_SZ);
827         int64_t err = diff(frm, dat, SWIDTH, SHEIGHT, lround(dx), lround(dy), lround(dm));
828         if( lerr > err ) { lerr = err;  lid = id; }
829         if( clips && err < pixlmt ) add_timelines(clips, id, err);
830         return 0;
831 }
832
833 int MediaDb::
834 add_timelines(Clips *clips, int fid, double err)
835 {
836         // found frame, find timelines
837         if( get_timelines(fid) ) {
838                 printf(_(" find timeline frame_id(%d) failed\n"), fid);
839                 return 1;
840         }
841
842         // process Timelines which contain Frame_id
843         int cid = -1;
844         for( int ret=0; !ret && timeline_frame_id()==fid; ret=next_timelines() ) {
845                 int tid = timeline_clip_id();
846                 if( tid != cid && clip_id(cid=tid) ) break;
847                 double start_time = position - timeline_offset();
848                 if( start_time < 0 ) continue;
849                 int group = timeline_group();
850                 if( (group & group_mask) == 0 ) continue;
851                 double length = clip_length();
852                 double end_time = start_time + length;
853 //printf("clip=%d, pos=%f, time=%f-%f, %s\n",
854 //  cid, position, start_time, end_time, clip_title());
855                 clips->check(cid, start_time, end_time, group, err);
856         }
857
858         return 0;
859 }
860