18 void write_pgm(uint8_t *tp, int w, int h, const char *fmt, ...)
20 va_list ap; va_start(ap, fmt);
21 char fn[256]; vsnprintf(fn, sizeof(fn), fmt, ap);
23 FILE *fp = !strcmp(fn,"-") ? stdout : fopen(fn,"w");
25 fprintf(fp,"P5\n%d %d\n255\n",w,h);
31 void write_ppm(uint8_t *tp, int w, int h, const char *fmt, ...)
33 va_list ap; va_start(ap, fmt);
34 char fn[256]; vsnprintf(fn, sizeof(fn), fmt, ap);
36 FILE *fp = !strcmp(fn,"-") ? stdout : fopen(fn,"w");
38 fprintf(fp,"P6\n%d %d\n255\n",w,h);
44 static inline int clip(int v, int mn, int mx)
46 return v<mn ? mn : v>mx ? mx : v;
49 void pbm_diff(uint8_t *ap, uint8_t *bp, int w, int h, const char *fmt, ...)
51 va_list va; va_start(va, fmt);
52 char fn[256]; vsnprintf(fn, sizeof(fn), fmt, va);
54 FILE *fp = !strcmp(fn,"-") ? stdout : fopen(fn,"w");
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);
67 scale(uint8_t *odata, int ow, int oh, int sx, int sy, int sw, int sh)
69 pw = (double)(ow * oh) / (rw * rh);
70 odat = odata + sy*ow + sx;
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;
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();
95 Deletions(int pid, const char *fn)
98 strcpy(this->filepath,fn);
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));
120 Deletions *Deletions::
121 read_dels(const char *filename)
124 if( xml.read_from_file(filename, 1) ) return 0;
126 if( xml.read_tag() ) return 0;
127 } while( !xml.tag.title_is("DELS") );
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;
141 xml.tag.set_title("DELS");
142 xml.tag.set_property("PID", pid);
143 xml.tag.set_property("FILE", filepath);
145 xml.append_newline();
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);
152 xml.tag.set_title("/DEL");
154 xml.append_newline();
157 xml.tag.set_title("/DELS");
159 xml.append_newline();
164 write_dels(const char *filename)
166 FILE *fp = fopen(filename, "wb");
167 if( !fp != 0 ) return 1;
170 xml.terminate_string();
171 xml.write_to_file(fp);
178 Clip(int clip_id, double start, double end, int index)
180 this->clip_id = clip_id;
194 Clips *clips = (Clips *)list;
195 if( clips && clips->current == this ) clips->current = 0;
198 Clip *Clips::locate(int id, double start, double end)
200 if( current && current->clip_id == id &&
201 end >= current->start && start < current->end )
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;
225 new_clip(int clip_id, double start, double end, int index)
227 return append(new Clip(clip_id, start, end, index));
231 get_clip(int idx, int &id, double &start, double &end)
233 int votes = -1, cid = -1;
234 double st = 0, et = 0;
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
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;
258 start = st; end = et; id = cid;
266 check(int clip_id, double start, double end, int group, double err)
268 Clip *clip = locate(clip_id, start, end);
270 if( err < clip->best ) {
272 if( start < clip->start ) clip->start = start;
273 if( end > clip->end ) clip->end = end;
277 clip = new_clip(clip_id, start, end, count++);
278 clip->groups |= group;
279 if( cycle != clip->cycle ) {
282 //printf("clip %d votes %d\n",clip_id,clip->votes);
290 xml.tag.set_title("CLIPS");
291 xml.tag.set_property("PID", pid);
293 xml.append_newline();
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);
303 xml.tag.set_title("/CLIP");
305 xml.append_newline();
308 xml.tag.set_title("/CLIPS");
310 xml.append_newline();
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++));
332 Snip(int clip_id, double start, double end, int index)
333 : Clip(clip_id, start, end, index)
353 new_clip(int clip_id, double start, double end, int index)
355 return append(new Snip(clip_id, start, end, index));
369 if( opened ) closeDb();
373 int MediaDb::is_open() { return opened > 0 ? opened : 0; }
374 int MediaDb::transaction() { return db->transaction(); }
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;
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(); }
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(); }
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();
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(); }
419 mean(uint8_t *dat, int n, int stride)
422 for( int i=0; i<n; ++i, dat+=stride ) s += *dat;
423 return (double)s / n;
427 variance(uint8_t *dat, double m, int n, int stride)
429 double ss = 0, s = 0;
430 for( int i=0; i<n; ++i, dat+=stride ) {
432 s += dx; ss += dx*dx;
434 return (ss - s*s / n) / (n-1);
438 centroid(uint8_t *dat, int n, int stride)
441 for( int i=0; i<n; dat+=stride ) { s += *dat; ss += ++i * *dat; }
442 return s > 0 ? (double)ss / s : n / 2.;
446 centroid(uint8_t *dat, int w, int h, double &xx, double &yy)
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;
456 deviation(uint8_t *a, uint8_t *b, int sz, int margin,
457 double &dev, int &ofs, int stride)
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; }
474 cmpr(uint8_t *ap, uint8_t *bp, int sz)
477 for( int i=sz; --i>=0; ++ap,++bp ) v += abs(*ap-*bp);
482 diff(uint8_t *a, uint8_t *b, int w, int h, int dx, int dy, int bias)
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 ) {
494 for( int x=ww; --x>=0; ++a, ++b ) { ierr += abs(*a-bias - *b); }
500 fit(int *dat, int n, double &a, double &b)
502 double st2 = 0, sb = 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 ) {
516 eq(uint8_t *sp, uint8_t *dp, int len)
519 int hist[256], wts[256], map[256];
520 for( int i=0; i<256; ++i ) hist[i] = 0;
522 for( int i=len; --i>=0; ++bp ) ++hist[*bp];
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;
529 fit(&wts[mn], mx-mn, a, b);
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];
535 if( sp != dp ) memcpy(dp,sp,len);
539 // record constructors
541 new_frame(int clip_id, uint8_t *dat, int no, int group, double offset)
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;
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();
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();
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)
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();
590 return new_clip_view(db->clip_set.id(), creation_time);
594 new_clip_view(int clip_id, int64_t access_time)
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();
605 access_clip(int clip_id, int64_t access_time)
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();
618 del_clip_set(int clip_id)
620 if( db->clip_set.FindId(clip_id) ) return 1;
622 db->clip_set.Destruct();
623 db->clip_set.Deallocate();
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();
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;
639 db->video_frame.Destruct();
640 db->video_frame.Deallocate();
646 // db file operations
650 if( opened > 0 || db->create(MEDIA_DB) ) {
651 printf("MediaDb::newDb failed\n");
660 if( opened > 0 ) return 0;
661 if( db->access(MEDIA_DB, MEDIA_SHM_KEY, rw) ) {
662 printf("MediaDb::openDb failed\n");
673 if( access(MEDIA_DB, F_OK) ) result = newDb();
674 if( !result ) result = openDb(rw);
681 if( opened > 0 ) { detachDb(); db->close(); }
688 if( opened > 0 ) db->commit();
694 if( opened > 0 ) db->undo();
700 return opened > 0 ? db->attach(rw) : -1;
706 return opened > 0 ? db->detach() : -1;
711 get_timelines(int frame_id)
713 return TimelineLoc::ikey_Timelines(db->timeline, frame_id).Locate();
719 return TimelineLoc::rkey_Timelines(db->timeline).Next();
723 get_sequences(int clip_id, int seq_no)
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;
733 return TimelineLoc::rkey_Sequences(db->timeline).Next();
738 get_image(int id, uint8_t *dat, int &w, int &h)
741 if( !db->video_frame.FindId(id) ) {
742 memcpy(dat, db->video_frame.Frame_data(), SFRM_SZ);
743 w = SWIDTH; h = SHEIGHT;
750 get_frame_key(uint8_t *dat, int &fid,
751 Clips *clips, double pos, int mask)
753 return get_media_key(dat, fid,
754 MEDIA_SEARCH_DIST, MEDIA_SEARCH_ERRLMT,
759 get_media_key(uint8_t *dat, int &fid, int dist, double errlmt,
760 Clips *clips, double pos, int mask)
762 fmean = mean(dat, SFRM_SZ);
763 if( fmean < 17 ) return -1; // black, forbidden
764 if( fmean > 239 ) return -1; // white, forbidden
766 pixlmt = SFRM_SZ*errlmt;
769 fsd = std_dev(dat, fmean, SFRM_SZ);
770 centroid(dat, SWIDTH, SHEIGHT, fcx, fcy);
772 eq(dat, frm, SFRM_SZ);
773 if( clips ) ++clips->cycle;
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;
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;
794 get_key(Clips *clips, Db::rKey &prev_rkey, Db::rKey &next_rkey)
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);
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);
807 return lerr < pixlmt ? 0 : 1;
811 key_compare(Clips *clips, Db::ObjectLoc &loc)
814 if( nid == id ) return 1;
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);
834 add_timelines(Clips *clips, int fid, double err)
836 // found frame, find timelines
837 if( get_timelines(fid) ) {
838 printf(_(" find timeline frame_id(%d) failed\n"), fid);
842 // process Timelines which contain Frame_id
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);