this->title = title;
this->do_alpha = do_alpha;
this->do_okcancel = 0;
- this->output = BLACK;
- this->alpha = 255;
+ this->output = this->orig_color = BLACK;
+ this->alpha = this->orig_alpha = 255;
}
ColorPicker::~ColorPicker()
}
return;
}
+ this->orig_color = output;
+ this->orig_alpha = alpha;
this->output = output;
this->alpha = alpha;
this->do_okcancel = do_okcancel;
void update_gui(int output, int alpha);
BC_Window* new_gui();
+ int orig_color, orig_alpha;
int output, alpha;
int do_alpha, do_okcancel;
const char *title;
lmt = bfr + bsz;
isz = max_size;
destroy = del;
+ share_lock = new Mutex("XMLBuffer::share_lock");
}
XMLBuffer::XMLBuffer(const char *buf, long buf_size, int del)
lmt = inp = bfr+bsz;
isz = bsz;
destroy = del;
+ share_lock = new Mutex("XMLBuffer::share_lock");
}
XMLBuffer::XMLBuffer(long buf_size, char *buf, int del)
lmt = outp = bfr+bsz;
isz = bsz;
destroy = del;
+ share_lock = new Mutex("XMLBuffer::share_lock");
}
XMLBuffer::~XMLBuffer()
{
if( destroy ) delete [] bfr;
+ delete share_lock;
}
int XMLBuffer::demand(long len)
void XMLBuffer::copy_from(XMLBuffer *xbuf)
{
- if( bsz != xbuf->bsz ) { delete [] bfr; bfr = 0; }
- if( !bfr ) bfr = new unsigned char[bsz = xbuf->bsz];
- lmt = bfr + bsz;
- long ilen = xbuf->otell(), olen = xbuf->itell();
- inp = pos(ilen);
- outp = pos(olen);
- if( ilen > 0 )
- memmove(bfr, xbuf->bfr, ilen);
- destroy = xbuf->destroy;
+ xbuf->share_lock->lock("XMLBuffer::copy_from");
+ share_lock->lock("XMLBuffer::copy_from");
+ oseek(0);
+ write((const char*)xbuf->pos(0), xbuf->otell());
+ iseek(xbuf->itell());
+ xbuf->share_lock->unlock();
+ share_lock->unlock();
}
FileXML::~FileXML()
{
if( !shared ) delete buffer;
+ else buffer->share_lock->unlock();
delete [] output;
}
strcpy(this->filename, "");
delete buffer;
buffer = xbuf;
+ xbuf->share_lock->lock("FileXML::set_shared_input");
xbuf->iseek(0);
- set_coding(coded);
shared = 1;
+ set_coding(coded);
return 0;
}
strcpy(this->filename, "");
delete buffer;
buffer = xbuf;
+ xbuf->share_lock->lock("FileXML::set_shared_output");
xbuf->oseek(0);
- set_coding(coded);
shared = 1;
+ set_coding(coded);
return 0;
}
#include <limits.h>
#include "arraylist.h"
+#include "mutex.h"
#include "keyframe.inc"
#include "filexml.inc"
#include "sizes.h"
long bsz, isz;
unsigned char *inp, *outp, *bfr, *lmt;
int destroy;
+ Mutex *share_lock;
int demand(long len);
friend class KeyFrame;
+ friend class FileXML;
public:
XMLBuffer(long buf_size=0x1000, long max_size=LONG_MAX, int del=1);
XMLBuffer(long buf_size, char *buf, int del=0); // writing
xbuf = new XMLBuffer();
}
+KeyFrame::KeyFrame(const char *buf, long len)
+ : Auto()
+{
+ xbuf = new XMLBuffer(buf, len, 0);
+}
+
KeyFrame::KeyFrame(EDL *edl, KeyFrames *autos)
: Auto(edl, (Autos*)autos)
{
KeyFrame &operator =(KeyFrame &k) { return k; } //illegal
public:
KeyFrame();
+ KeyFrame(const char *buf, long len);
KeyFrame(EDL *edl, KeyFrames *autos);
virtual ~KeyFrame();
#include "track.h"
#include "transportque.inc"
-
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
#include <ctype.h>
#include <errno.h>
-#include <string.h>
-
-
-
PluginClientThread::PluginClientThread(PluginClient *client)
using_defaults = 1;
//printf("PluginClient::load_defaults_xml %d %s\n", __LINE__, path);
- KeyFrame temp_keyframe;
- FILE *fp = fopen(path, "r");
- if( fp ) {
- struct stat st; int fd = fileno(fp);
- int64_t sz = !fstat(fd, &st) ? st.st_size : BCTEXTLEN;
- char *data = temp_keyframe.get_data(sz+1);
- int data_size = fread(data, 1, sz, fp);
- if( data_size < 0 ) data_size = 0;
- if( data_size > 0 ) {
- data[data_size] = 0;
- temp_keyframe.xbuf->oseek(data_size);
+ char *data = 0;
+ int64_t len = -1;
+ struct stat st;
+ int fd = open(path, O_RDONLY);
+ if( fd >= 0 && !fstat(fd, &st) ) {
+ int64_t sz = st.st_size;
+ data = new char[sz+1];
+ len = read(fd, data, sz);
+ close(fd);
+ }
+ if( data && len >= 0 ) {
+ data[len] = 0;
// Get window extents
- int i = 0;
- for( int state=0; i<(data_size-8) && state>=0; ++i ) {
- if( !data[i] || data[i] == '<' ) break;
- if( !isdigit(data[i]) ) continue;
- if( !state ) {
- window_x = atoi(data + i);
- state = 1;
- }
- else {
- window_y = atoi(data + i);
- state = -1;
- }
- while( i<data_size && isdigit(data[i]) ) ++i;
+ int i = 0;
+ for( int state=0; i<len && state>=0; ++i ) {
+ if( !data[i] || data[i] == '<' ) break;
+ if( !isdigit(data[i]) ) continue;
+ if( !state ) {
+ window_x = atoi(data+i);
+ state = 1;
+ }
+ else {
+ window_y = atoi(data+i);
+ state = -1;
}
- temp_keyframe.xbuf->iseek(i);
- read_data(&temp_keyframe);
+ while( i<len && isdigit(data[i]) ) ++i;
}
-
- fclose(fp);
+ KeyFrame keyframe(data+i, len-i);
+ read_data(&keyframe);
}
+ delete [] data;
+
using_defaults = 0;
//printf("PluginClient::load_defaults_xml %d %s\n", __LINE__, path);
}
[ . ])
PKG_3RD([ffmpeg],[yes],
- [ffmpeg-4.0],
+ [ffmpeg-4.1],
[ libavutil/libavutil.a \
libavcodec/libavcodec.a \
libpostproc/libpostproc.a \
dbtv: dbtv.C
$(CXX) $(CXXFLAGS) $(LDFLAGS) dbtv.C $(LDLIBS) -lX11 -o $@
-XTV_MEDIA_OBJS := $(TOPDIR)/cinelerra/$(OBJDIR)/mediadb.o $(TOPDIR)/cinelerra/$(OBJDIR)/filexml.o
+XTV_MEDIA_OBJS := $(TOPDIR)/cinelerra/$(OBJDIR)/mediadb.o
+XTV_MEDIA_OBJS += $(TOPDIR)/cinelerra/$(OBJDIR)/filexml.o
+XTV_MEDIA_OBJS += $(TOPDIR)/guicast/$(OBJDIR)/mutex.o
+XTV_MEDIA_OBJS += $(TOPDIR)/guicast/$(OBJDIR)/bctrace.o
XTV_LIBS := $(TOPDIR)/libzmpeg3/$(OBJDIR)/libzmpeg3.a
XTV_LIBS += $(TOPDIR)/db/$(OBJDIR)/db.a
<tr>
<td height="29" align="left"><b><font face="Liberation Serif" size=5>Main Menu</font></b></td>
<td align="left"><font face="Liberation Serif" size=3>(- is a checkbox)</font></td>
- <td align="left" sdnum="1033;0;MM/DD/YY"><font face="Liberation Serif" size=3>06/26/2018 update</font></td>
+ <td align="left" sdnum="1033;0;MM/DD/YY"><font face="Liberation Serif" size=3>11/08/2018 update</font></td>
<td align="left"><font face="Liberation Serif"><br></font></td>
</tr>
<tr>
<td align="left"><font face="Liberation Serif" size=4>Double click</font></td>
<td align="left"><font face="Liberation Serif" size=4>On fade/speed, synch video/audio ganged</font></td>
</tr>
+ <tr>
+ <td height="26" align="left"><font face="Liberation Serif" size=4><br></font></td>
+ <td align="left"><font face="Liberation Serif" size=4><br></font></td>
+ <td align="left"><font face="Liberation Serif" size=4>Double MMB</font></td>
+ <td align="left"><font face="Liberation Serif" size=4>On fade/speed, select keyframe position</font></td>
+ </tr>
<tr>
<td height="26" align="left"><font face="Liberation Serif" size=4><br></font></td>
<td align="left"><font face="Liberation Serif" size=4>'U'</font></td>
<td align="left"><font face="Liberation Serif" size=4><br></font></td>
<td align="left"><font face="Liberation Serif" size=4><br></font></td>
</tr>
+ <tr>
+ <td height="26" align="left"><b><font face="Liberation Serif" size=4> Patchbay</b></font></td>
+ <td align="left"><font face="Liberation Serif" size=4>Shift/Move</font></td>
+ <td align="left"><font face="Liberation Serif" size=4>Hold LMB</font></td>
+ <td align="left"><font face="Liberation Serif" size=4>On Fade slider bar, sets gain to 100% or 0db</font></td>
+ </tr>
+ <tr>
+ <td height="26" align="left"><font face="Liberation Serif" size=4><br></font></td>
+ <td align="left"><font face="Liberation Serif" size=4><br></font></td>
+ <td align="left"><font face="Liberation Serif" size=4><br></font></td>
+ <td align="left"><font face="Liberation Serif" size=4><br></font></td>
+ </tr>
<tr>
<td height="26" align="left"><b><font face="Liberation Serif" size=4>Compositor</font></b></td>
<td align="left"><font face="Liberation Serif" size=4><br></font></td>
; new in 4.0
#acontrast Contrast=33
#afir
-aiir
#convolve
#deconvolve
drmeter
setrange
#unpremultiply
vfrdet
+; new in 4.1
+acue
+#adeclick
+#adeclip
+aderivative
+afftdn
+aintegral
+amplify
+chromahold
+cue
+deblock
+fttdnoiz
+#graphmonitor
+greyedge
+#highshelf
+#lowshelf
+lut1d
+pal75bars
+pal100bars
+setparams
+#sinc
+tmix
+vibrance
+; broken in 4.1
+#acrossover
+#aiir
+#amultiply
+#bm3d
+#sr
+#xstack
+#agraphmonitor
highlighted = popup_down = 0;
menu_popup = 0;
icon = 0;
- if(margin >= 0)
- this->margin = margin;
- else
- this->margin = BC_WindowBase::get_resources()->popupmenu_margin;
-
+ this->margin = margin >= 0 ? margin :
+ BC_WindowBase::get_resources()->popupmenu_margin;
this->use_title = use_title;
strcpy(this->text, text);
for(int i = 0; i < TOTAL_IMAGES; i++)
highlighted = popup_down = 0;
menu_popup = 0;
icon = 0;
+ this->margin = BC_WindowBase::get_resources()->popupmenu_margin;
this->use_title = use_title;
strcpy(this->text, text);
for(int i = 0; i < TOTAL_IMAGES; i++)
BC_TumbleTextBox::BC_TumbleTextBox(BC_WindowBase *parent_window,
- int64_t default_value,
- int64_t min,
- int64_t max,
- int x,
- int y,
- int text_w)
+ int64_t default_value, int64_t min, int64_t max,
+ int x, int y, int text_w)
{
reset();
this->x = x;
}
BC_TumbleTextBox::BC_TumbleTextBox(BC_WindowBase *parent_window,
- int default_value,
- int min,
- int max,
- int x,
- int y,
- int text_w)
+ int default_value, int min, int max,
+ int x, int y, int text_w)
{
reset();
this->x = x;
}
BC_TumbleTextBox::BC_TumbleTextBox(BC_WindowBase *parent_window,
- float default_value_f,
- float min_f,
- float max_f,
- int x,
- int y,
- int text_w)
+ float default_value_f, float min_f, float max_f,
+ int x, int y, int text_w, int precision)
{
reset();
this->x = x;
this->max_f = max_f;
this->default_value_f = default_value_f;
this->text_w = text_w;
+ this->precision = precision;
this->parent_window = parent_window;
use_float = 1;
- precision = 4;
increment = 1;
}
{
public:
BC_TumbleTextBox(BC_WindowBase *parent_window,
- int64_t default_value,
- int64_t min,
- int64_t max,
- int x,
- int y,
- int text_w);
+ int64_t default_value, int64_t min, int64_t max,
+ int x, int y, int text_w);
BC_TumbleTextBox(BC_WindowBase *parent_window,
- int default_value,
- int min,
- int max,
- int x,
- int y,
- int text_w);
+ int default_value, int min, int max,
+ int x, int y, int text_w);
BC_TumbleTextBox(BC_WindowBase *parent_window,
- float default_value,
- float min,
- float max,
- int x,
- int y,
- int text_w);
+ float default_value, float min, float max,
+ int x, int y, int text_w, int precision=4);
virtual ~BC_TumbleTextBox();
int create_objects();
int get_dragging();
wchar_t* get_wkeystring(int *length = 0);
int get_keypress();
+ int get_keysym() { return keysym; }
#ifdef X_HAVE_UTF8_STRING
char* get_keypress_utf8();
#endif
--- /dev/null
+#include "bcsignals.h"
+#include "guicast.h"
+#include "pys_icon_png.h"
+
+class TestList : public BC_ListBox
+{
+public:
+ TestList(int x, int y, int w, int h,
+ ArrayList<BC_ListBoxItem*> *items);
+ int handle_event();
+ int selection_changed();
+};
+
+TestList::TestList(int x, int y, int w, int h,
+ ArrayList<BC_ListBoxItem*> *items)
+ : BC_ListBox(x, y, w, h, LISTBOX_TEXT, items,
+ 0, 0, 1, 0, 0, LISTBOX_SINGLE, ICON_LEFT, 0)
+{
+}
+
+int TestList::handle_event()
+{
+ printf("handle_event\n");
+ return 1;
+}
+
+int TestList::selection_changed()
+{
+ BC_ListBoxItem *item = get_selection(0, 0);
+ printf("selection_changed %s\n", !item ? "<nul>" : item->get_text());
+ return 1;
+}
+
+class TestWindow : public BC_Window
+{
+public:
+ TestWindow() : BC_Window("Test9", 0, 0, 320, 240) {};
+ void create_objects();
+ int keypress_event();
+ BC_ListBox *list;
+ ArrayList<BC_ListBoxItem*> items;
+};
+
+void TestWindow::create_objects()
+{
+ lock_window("AWindowRemovePluginGUI::create_objects");
+ set_color(BLACK);
+ set_font(LARGEFONT);
+ int x = 10, y = 20;
+ draw_text(x, y, "Hello world");
+ y += 25;
+ BC_Button *ok_button = new BC_OKButton(this);
+ add_subwindow(ok_button);
+ BC_Button *cancel_button = new BC_CancelButton(this);
+ add_subwindow(cancel_button);
+ BC_ListBoxItem *thing;
+ ArrayList<BC_ListBoxItem*> *sublist;
+ items.append(thing = new BC_ListBoxItem("thing 1"));
+ VFrame *pys_icon = new VFramePng(pys_icon_png);
+ thing->set_icon_vframe(pys_icon);
+ int pw = pys_icon->get_w(), ph = pys_icon->get_h();
+ BC_Pixmap *pys_picon = new BC_Pixmap(this, pw, ph);
+ pys_picon->draw_vframe(pys_icon, 0, 0, pw, pw, 0, 0);
+ thing->set_icon(pys_picon);
+ sublist = thing->new_sublist(1);
+ BC_ListBoxItem *fish, *cat, *hat;
+ sublist->append(fish = new BC_ListBoxItem("fish"));
+ ArrayList<BC_ListBoxItem*> *fish_list = fish->new_sublist(1);
+ fish_list->append(new BC_ListBoxItem("green"));
+ fish_list->append(new BC_ListBoxItem("eggs"));
+ fish_list->append(new BC_ListBoxItem("ham"));
+ sublist->append(cat = new BC_ListBoxItem("cat"));
+ ArrayList<BC_ListBoxItem*> *cat_list = cat->new_sublist(1);
+ cat_list->append(new BC_ListBoxItem("videos"));
+ sublist->append(hat = new BC_ListBoxItem("hat"));
+ ArrayList<BC_ListBoxItem*> *hat_list = hat->new_sublist(1);
+ hat_list->append(new BC_ListBoxItem("bonnet"));
+ hat_list->append(new BC_ListBoxItem("cap"));
+ hat_list->append(new BC_ListBoxItem("sombrero"));
+ items.append(thing = new BC_ListBoxItem("thing 2"));
+ int lw = get_w()-x-10, lh = ok_button->get_y() - y - 5;
+ add_subwindow(list = new TestList(x, y, lw, lh, &items));
+ show_window();
+ unlock_window();
+}
+
+int TestWindow::keypress_event()
+{
+ switch( get_keypress() ) {
+ case 'v':
+ switch( list->get_format() ) {
+ case LISTBOX_TEXT:
+ list->update_format(LISTBOX_ICONS, 1);
+ break;
+ case LISTBOX_ICONS:
+ list->update_format(LISTBOX_ICONS_PACKED, 1);
+ break;
+ case LISTBOX_ICONS_PACKED:
+ list->update_format(LISTBOX_ICON_LIST, 1);
+ break;
+ case LISTBOX_ICON_LIST:
+ list->update_format(LISTBOX_TEXT, 1);
+ break;
+ }
+ break;
+ }
+ return 1;
+}
+
+int main()
+{
+ new BC_Signals;
+ TestWindow window;
+ window.create_objects();
+ window.run_window();
+}
+
rumbler \
scale \
scaleratio \
+ sketcher \
seltempavg \
shapewipe \
sharpen \
rotate \
rumbler \
scale \
+ sketcher \
shapewipe \
sharpen \
shiftinterlace \
void CriKey::update_gui()
{
if( !thread ) return;
- if( !load_configuration() ) return;
thread->window->lock_window("CriKey::update_gui");
CriKeyWindow *window = (CriKeyWindow*)thread->window;
- window->update_gui();
- window->flush();
+ if( load_configuration() ) {
+ window->update_gui();
+ window->flush();
+ }
thread->window->unlock_window();
}
CriKeyWindow::~CriKeyWindow()
{
+ delete point_x;
+ delete point_y;
}
void CriKeyWindow::create_objects()
pending_config = 0;
plugin->send_configure_change();
}
-int CriKeyWindow::check_configure_change(int ret)
+
+int CriKeyWindow::grab_event(XEvent *event)
{
+ int ret = do_grab_event(event);
if( pending_config && !grab_event_count() )
send_configure_change();
return ret;
}
-int CriKeyWindow::grab_event(XEvent *event)
+int CriKeyWindow::do_grab_event(XEvent *event)
{
switch( event->type ) {
case ButtonPress: break;
case ButtonRelease: break;
case MotionNotify: break;
default:
- return check_configure_change(0);
+ return 0;
}
MWindow *mwindow = plugin->server->mwindow;
if( !dragging ) {
if( cx < 0 || cx >= mwindow->theme->ccanvas_w ||
cy < 0 || cy >= mwindow->theme->ccanvas_h )
- return check_configure_change(0);
+ return 0;
}
switch( event->type ) {
case ButtonPress:
- if( dragging ) return check_configure_change(0);
+ if( dragging ) return 0;
if( event->xbutton.button == WHEEL_UP ) return threshold->wheel_event(1);
if( event->xbutton.button == WHEEL_DOWN ) return threshold->wheel_event(-1);
dragging = event->xbutton.state & ShiftMask ? -1 : 1;
break;
case ButtonRelease:
- if( !dragging ) return check_configure_change(0);
+ if( !dragging ) return 0;
dragging = 0;
return 1;
case MotionNotify:
- if( !dragging ) return check_configure_change(0);
+ if( !dragging ) return 0;
break;
default:
- return check_configure_change(0);
+ return 0;
}
float cursor_x = cx, cursor_y = cy;
if( hot_point >= 0 && sz > 0 ) {
CriKeyPoint *pt = points[hot_point];
point_list->set_point(hot_point, PT_X, pt->x = output_x);
+ point_list->set_point(hot_point, PT_Y, pt->y = output_y);
for( int i=0; i<sz; ++i ) {
pt = points[i];
pt->e = i==hot_point ? !pt->e : 0;
}
last_x = output_x; last_y = output_y;
- if( !grab_event_count() )
- send_configure_change();
- else
- pending_config = 1;
+ pending_config = 1;
return 1;
}
void update_gui();
void start_color_thread();
int grab_event(XEvent *event);
+ int do_grab_event(XEvent *event);
void done_event(int result);
- int check_configure_change(int ret);
void send_configure_change();
CriKey *plugin;
--- /dev/null
+include ../../plugin_defs
+
+OBJS := \
+ $(OBJDIR)/sketcher.o \
+ $(OBJDIR)/sketcherwindow.o
+
+PLUGIN = sketcher
+
+include ../../plugin_config
+
+$(OBJDIR)/sketcher.o: sketcher.C
+$(OBJDIR)/sketcherwindow.o: sketcherwindow.C
+
--- /dev/null
+/*
+ * CINELERRA
+ * Copyright (C) 1997-2015 Adam Williams <broadcast at earthling dot net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include<stdio.h>
+#include<stdint.h>
+#include<math.h>
+#include<string.h>
+
+#include "arraylist.h"
+#include "bccmodels.h"
+#include "bccolors.h"
+#include "clip.h"
+#include "edlsession.h"
+#include "filexml.h"
+#include "sketcher.h"
+#include "sketcherwindow.h"
+#include "language.h"
+#include "vframe.h"
+
+void SketcherPoint::init(int id, int x, int y)
+{
+ this->id = id;
+ this->x = x; this->y = y;
+}
+SketcherPoint::SketcherPoint(int id)
+{
+ init(id, 0, 0);
+}
+SketcherPoint::SketcherPoint(int id, int x, int y)
+{
+ init(id, x, y);
+}
+SketcherPoint::~SketcherPoint()
+{
+}
+SketcherPoint::SketcherPoint(SketcherPoint &pt)
+{
+ copy_from(pt);
+}
+int SketcherPoint::equivalent(SketcherPoint &that)
+{
+ return this->id == that.id &&
+ this->x == that.x &&
+ this->y == that.y ? 1 : 0;
+}
+void SketcherPoint::copy_from(SketcherPoint &that)
+{
+ this->id = that.id;
+ this->x = that.x; this->y = that.y;
+}
+void SketcherPoint::save_data(FileXML &output)
+{
+ char point[BCSTRLEN];
+ sprintf(point,"/POINT_%d",id);
+ output.tag.set_title(point+1);
+ output.tag.set_property("X", x);
+ output.tag.set_property("Y", y);
+ output.append_tag();
+ output.tag.set_title(point+0);
+ output.append_tag();
+ output.append_newline();
+}
+void SketcherPoint::read_data(FileXML &input)
+{
+ id = atoi(input.tag.get_title() + 6);
+ x = input.tag.get_property("X", 0.f);
+ y = input.tag.get_property("Y", 0.f);
+}
+
+void SketcherCurve::init(int id, int ty, int radius, int pen, int color)
+{
+ this->id = id;
+ this->ty = ty;
+ this->radius = radius;
+ this->pen = pen;
+ this->color = color;
+}
+SketcherCurve::SketcherCurve(int id)
+{
+ init(id, 0, 1, 0, BLACK);
+}
+SketcherCurve::SketcherCurve(int id, int ty, int radius,int pen, int color)
+{
+ init(id, ty, radius, pen, color);
+}
+SketcherCurve::~SketcherCurve()
+{
+}
+SketcherCurve::SketcherCurve(SketcherCurve &cv)
+{
+ copy_from(cv);
+}
+int SketcherCurve::equivalent(SketcherCurve &that)
+{
+ if( this->id != that.id ) return 0;
+ if( this->ty != that.ty ) return 0;
+ if( this->radius != that.radius ) return 0;
+ if( this->pen != that.pen ) return 0;
+ if( this->color != that.color ) return 0;
+ int n = this->points.size();
+ if( n != that.points.size() ) return 0;
+ for( int i=0; i<n; ++i ) {
+ if( !points[i]->equivalent(*that.points[i]) ) return 0;
+ }
+ return 1;
+}
+void SketcherCurve::copy_from(SketcherCurve &that)
+{
+ this->id = that.id;
+ this->ty = that.ty;
+ this->radius = that.radius;
+ this->pen = that.pen;
+ this->color = that.color;
+ int m = points.size(), n = that.points.size();
+ while( m > n ) points.remove_object_number(--m);
+ while( m < n ) { points.append(new SketcherPoint()); ++m; }
+ for( int i=0; i<n; ++i ) points[i]->copy_from(*that.points[i]);
+}
+void SketcherCurve::save_data(FileXML &output)
+{
+ this->ty = ty;
+ this->pen = pen; this->color = color;
+ char curve[BCSTRLEN];
+ sprintf(curve,"/CURVE_%d",id);
+ output.tag.set_title(curve+1);
+ output.tag.set_property("TYPE", ty);
+ output.tag.set_property("RADIUS", radius);
+ output.tag.set_property("PEN", pen);
+ output.tag.set_property("COLOR", color);
+ output.append_tag();
+ output.append_newline();
+ for( int i=0,n=points.size(); i<n; ++i )
+ points[i]->save_data(output);
+ output.tag.set_title(curve+0);
+ output.append_tag();
+ output.append_newline();
+}
+void SketcherCurve::read_data(FileXML &input)
+{
+ id = atoi(input.tag.get_title() + 6);
+ ty = input.tag.get_property("TYPE", 0);
+ radius = input.tag.get_property("RADIUS", 1.);
+ pen = input.tag.get_property("PEN", 0);
+ color = input.tag.get_property("COLOR", BLACK);
+}
+
+int Sketcher::new_curve(int ty, int radius, int pen, int color)
+{
+ SketcherCurves &curves = config.curves;
+ int k = curves.size(), id = 1;
+ for( int i=k; --i>=0; ) {
+ int n = config.curves[i]->id;
+ if( n >= id ) id = n + 1;
+ }
+ SketcherCurve *cv = new SketcherCurve(id, ty, radius, pen, color);
+ curves.append(cv);
+ config.cv_selected = k;
+ return k;
+}
+
+int Sketcher::new_curve()
+{
+ return new_curve(0, 1, 0, BLACK);
+}
+
+int Sketcher::new_point(SketcherCurve *cv, int x, int y)
+{
+ int k = cv->points.size(), id = 1;
+ for( int i=k; --i>=0; ) {
+ int n = cv->points[i]->id;
+ if( n >= id ) id = n + 1;
+ }
+ SketcherPoint *pt = new SketcherPoint(id, x, y);
+ cv->points.append(pt);
+ return k;
+}
+
+int Sketcher::new_point()
+{
+ int ci = config.cv_selected;
+ if( ci < 0 || ci >= config.curves.size() )
+ return -1;
+ SketcherCurve *cv = config.curves[ci];
+ EDLSession *session = get_edlsession();
+ int x = !session ? 0.f : session->output_w / 2.f;
+ int y = !session ? 0.f : session->output_h / 2.f;
+ return new_point(cv, x, y);
+}
+
+REGISTER_PLUGIN(Sketcher)
+
+SketcherConfig::SketcherConfig()
+{
+ drag = 1;
+ cv_selected = 0;
+ pt_selected = 0;
+}
+SketcherConfig::~SketcherConfig()
+{
+}
+
+int SketcherConfig::equivalent(SketcherConfig &that)
+{
+ if( this->drag != that.drag ) return 0;
+ if( this->cv_selected != that.cv_selected ) return 0;
+ if( this->pt_selected != that.pt_selected ) return 0;
+ if( this->curves.size() != that.curves.size() ) return 0;
+ for( int i=0, n=curves.size(); i<n; ++i ) {
+ if( !curves[i]->equivalent(*that.curves[i]) ) return 0;
+ }
+ return 1;
+}
+
+void SketcherConfig::copy_from(SketcherConfig &that)
+{
+ this->drag = that.drag;
+ this->cv_selected = that.cv_selected;
+ this->pt_selected = that.pt_selected;
+ int m = curves.size(), n = that.curves.size();
+ while( m > n ) curves.remove_object_number(--m);
+ while( m < n ) { curves.append(new SketcherCurve()); ++m; }
+ for( int i=0; i<n; ++i ) curves[i]->copy_from(*that.curves[i]);
+}
+
+void SketcherConfig::interpolate(SketcherConfig &prev, SketcherConfig &next,
+ long prev_frame, long next_frame, long current_frame)
+{
+ this->cv_selected = prev.cv_selected;
+ this->pt_selected = prev.pt_selected;
+ this->drag = prev.drag;
+
+ double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
+ double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
+
+ curves.remove_all_objects();
+ int prev_cv_sz = prev.curves.size();
+ int next_cv_sz = next.curves.size();
+ for( int i=0; i<prev_cv_sz; ++i ) {
+ SketcherCurve *pcv = prev.curves[i], *ncv = 0;
+ SketcherCurve *cv = curves.append(new SketcherCurve());
+ int k = next_cv_sz; // associated by id in next
+ while( --k >= 0 && pcv->id != (ncv=next.curves[k])->id );
+ if( k >= 0 ) {
+ cv->id = pcv->id;
+ cv->ty = pcv->ty;
+ cv->radius = pcv->radius;
+ cv->pen = pcv->pen;
+ cv->color = pcv->color;
+ int prev_pt_sz = pcv->points.size(), next_pt_sz = ncv->points.size();
+ for( int j=0; j<prev_pt_sz; ++j ) {
+ SketcherPoint &pt = *pcv->points[j], *nt = 0;
+ k = next_pt_sz; // associated by id in next
+ while( --k >= 0 && pt.id != (nt=ncv->points[k])->id );
+ int x = pt.x, y = pt.y;
+ if( k >= 0 ) {
+ x = x * prev_scale + nt->x * next_scale;
+ y = y * prev_scale + nt->y * next_scale;
+ }
+ cv->points.append(new SketcherPoint(pt.id, x, y));
+ }
+ }
+ else
+ cv->copy_from(*pcv);
+ }
+}
+
+void SketcherConfig::limits()
+{
+}
+
+
+Sketcher::Sketcher(PluginServer *server)
+ : PluginVClient(server)
+{
+}
+
+Sketcher::~Sketcher()
+{
+}
+
+const char* Sketcher::plugin_title() { return N_("Sketcher"); }
+int Sketcher::is_realtime() { return 1; }
+
+NEW_WINDOW_MACRO(Sketcher, SketcherWindow);
+LOAD_CONFIGURATION_MACRO(Sketcher, SketcherConfig)
+
+void Sketcher::save_data(KeyFrame *keyframe)
+{
+ FileXML output;
+// cause data to be stored directly in text
+ output.set_shared_output(keyframe->xbuf);
+
+ output.tag.set_title("SKETCHER");
+ output.tag.set_property("DRAG", config.drag);
+ output.tag.set_property("CURVE_SELECTED", config.cv_selected);
+ output.tag.set_property("POINT_SELECTED", config.pt_selected);
+ output.append_tag();
+ output.append_newline();
+ for( int i=0,n=config.curves.size(); i<n; ++i ) {
+ config.curves[i]->save_data(output);
+ }
+ output.tag.set_title("/SKETCHER");
+ output.append_tag();
+ output.append_newline();
+ output.terminate_string();
+}
+
+void Sketcher::read_data(KeyFrame *keyframe)
+{
+ FileXML input;
+ input.set_shared_input(keyframe->xbuf);
+ config.curves.remove_all_objects();
+ int result = 0;
+ SketcherCurve *cv = 0;
+
+ while( !(result=input.read_tag()) ) {
+ if( input.tag.title_is("SKETCHER") ) {
+ config.drag = input.tag.get_property("DRAG", config.drag);
+ config.cv_selected = input.tag.get_property("CV_SELECTED", 0);
+ config.pt_selected = input.tag.get_property("PT_SELECTED", 0);
+ }
+ else if( !strncmp(input.tag.get_title(),"CURVE_",6) ) {
+ cv = new SketcherCurve();
+ cv->read_data(input);
+ config.curves.append(cv);
+ }
+ else if( !strncmp(input.tag.get_title(),"/CURVE_",7) )
+ cv = 0;
+ else if( !strncmp(input.tag.get_title(),"POINT_",6) ) {
+ if( cv ) {
+ SketcherPoint *pt = new SketcherPoint();
+ pt->read_data(input);
+ cv->points.append(pt);
+ }
+ else
+ printf("Sketcher::read_data: no curve for point\n");
+ }
+ }
+
+ if( !config.curves.size() ) {
+ new_curve(0, 1, 0, BLACK);
+ }
+ config.limits();
+}
+
+void Sketcher::update_gui()
+{
+ if( !thread ) return;
+ thread->window->lock_window("Sketcher::update_gui");
+ if( load_configuration() ) {
+ SketcherWindow *window = (SketcherWindow*)thread->window;
+ window->update_gui();
+ window->flush();
+ }
+ thread->window->unlock_window();
+}
+
+void Sketcher::draw_point(VFrame *vfrm, SketcherPoint *pt, int color, int d)
+{
+ int r = d/2+1, x = pt->x, y = pt->y;
+ vfrm->draw_smooth(x-r,y+0, x-r, y-r, x+0,y-r);
+ vfrm->draw_smooth(x+0,y-r, x+r, y-r, x+r,y+0);
+ vfrm->draw_smooth(x+r,y+0, x+r, y+r, x+0,y+r);
+ vfrm->draw_smooth(x+0,y+r, x-r, y+r, x-r,y+0);
+ vfrm->set_pixel_color(color);
+ vfrm->draw_x(pt->x, pt->y, d);
+}
+void Sketcher::draw_point(VFrame *vfrm, SketcherPoint *pt, int color)
+{
+ draw_point(vfrm, pt, color, bmax(w,h)/200 + 2);
+}
+
+
+int SketcherPenSquare::draw_pixel(int x, int y)
+{
+ vfrm->draw_line(x-n, y, x+n, y);
+ for( int i=-n; i<n; ++i )
+ vfrm->draw_line(x-n, y+i, x+n, y+i);
+ return 0;
+}
+int SketcherPenPlus::draw_pixel(int x, int y)
+{
+ if( n > 1 ) {
+ vfrm->draw_line(x-n, y, x+n, y);
+ vfrm->draw_line(x, y-n, x, y+n);
+ }
+ else
+ vfrm->draw_pixel(x, y);
+ return 0;
+}
+int SketcherPenSlant::draw_pixel(int x, int y)
+{
+ vfrm->draw_line(x-n, y+n, x+n, y-n);
+ vfrm->draw_line(x-n+1, y+n, x+n+1, y-n);
+ vfrm->draw_line(x-n, y+n+1, x+n, y-n+1);
+ return 0;
+}
+int SketcherPenXlant::draw_pixel(int x, int y)
+{
+ vfrm->draw_line(x-n, y+n, x+n, y-n);
+ vfrm->draw_line(x-n+1, y+n, x+n+1, y-n);
+ vfrm->draw_line(x-n, y+n+1, x+n, y-n+1);
+ vfrm->draw_line(x-n, y-n, x+n, y+n);
+ vfrm->draw_line(x-n+1, y-n, x+n+1, y+n);
+ vfrm->draw_line(x-n, y-n+1, x+n, y-n+1);
+ return 0;
+}
+
+
+VFrame *SketcherCurve::new_vpen(VFrame *out)
+{
+ switch( pen ) {
+ case PEN_SQUARE: return new SketcherPenSquare(out, radius);
+ case PEN_PLUS: return new SketcherPenPlus(out, radius);
+ case PEN_SLANT: return new SketcherPenSlant(out, radius);
+ case PEN_XLANT: return new SketcherPenXlant(out, radius);
+ }
+ return 0;
+}
+
+void SketcherCurve::draw_line(VFrame *out)
+{
+ SketcherPoint *pt0 = points[0];
+ VFrame *vpen = new_vpen(out);
+ out->set_pixel_color(color);
+ int n = points.size();
+ if( n >= 2 ) {
+ for( int pi=1; pi<n; ++pi ) {
+ SketcherPoint *pt1 = points[pi];
+ vpen->draw_line(pt0->x, pt0->y, pt1->x, pt1->y);
+ pt0 = pt1;
+ }
+ }
+ else
+ vpen->draw_pixel(pt0->x, pt0->y);
+ delete vpen;
+}
+
+/*
+# python
+from sympy import *
+var("x,y, ax,ay, bx,by, cx,cy, dx,dy")
+
+var("abdx,abdy, acdx,acdy, bddx,bddy, cddx,cddy");
+abdx = bx-ax; abdy = by-ay;
+acdx = cx-ax; acdy = cy-ay;
+bddx = dx-bx; bddy = dy-by;
+cddx = dx-cx; cddy = dy-cy;
+
+var("xc,yc, xd,yd, sx,sy");
+xc = (bx+dx)/2; yc = (by+dy)/2;
+xd = cx-xc; yd = cy-yc;
+ax = xc-xd; ay = yc-yd;
+# line thru b with slope (c-a) intersects line thru c with slope (d-b)
+sx = solve(((x - bx) * acdy/acdx + by) - ((x - cx) * bddy/bddx + cy),x)
+sy = solve(((y - by) * acdx/acdy + bx) - ((y - cy) * bddx/bddy + cx),y)
+
+var("zx,zy, zdx,zdy, sx,sy, px,py, qx,qy");
+# point z = (b+c)/2
+zx = (bx+cx)/2; zy = (by+cy)/2;
+zdx = (abdx+cddx)/2; zdy = (abdy+cddy)/2;
+# line thru z with slope (d-a) intersects line thru b with slope (c-a)
+px = solve(((x-zx)*zdy/zdx + zy) - ((x-bx) * acdy/acdx + by),x);
+py = solve(((y-zy)*zdx/zdy + zx) - ((y-by) * acdx/acdy + bx),y);
+# line thru z with slope (c-a + d-b)/2 intersects line thru c with slope (d-b)
+qx = solve(((x-zx)*zdy/zdx + zy) - ((x-cx) * bddy/bddx + cy),x);
+qy = solve(((y-zy)*zdx/zdy + zx) - ((y-cy) * bddx/bddy + cx),y);
+*/
+
+static void smooth_sxy(
+ float ax, float ay, float bx, float by,
+ float cx, float cy, float dx, float dy,
+ float &sx, float &sy)
+{
+ float acdx = cx-ax, acdy = cy-ay;
+ float bddx = dx-bx, bddy = dy-by;
+ float d = acdx*bddy - acdy*bddx;
+ if( fabsf(d) < 1 ) d = 1;
+ sx = (acdx*bddx*by - acdx*bddx*cy + acdx*bddy*cx - acdy*bddx*bx) / d;
+ sy = (acdx*bddy*by - acdy*bddx*cy - acdy*bddy*bx + acdy*bddy*cx) / d;
+ bclamp(sx, -32767.f, 32767.f);
+ bclamp(sy, -32767.f, 32767.f);
+}
+
+static void smooth_pxy(
+ float ax, float ay, float bx, float by,
+ float cx, float cy, float dx, float dy,
+ float &px, float &py)
+{
+ float abdx = bx - ax, abdy = by - ay;
+ float acdx = cx - ax, acdy = cy - ay;
+ float cddx = dx - cx, cddy = dy - cy;
+ float d = (2*(abdx*acdy - abdy*acdx - acdx*cddy + acdy*cddx));
+ if( fabsf(d) < 1 ) d = 1;
+ px = (-abdx*acdx*by + abdx*acdx*cy + 2*abdx*acdy*bx - abdy*acdx*bx - abdy*acdx*cx -
+ acdx*bx*cddy - acdx*by*cddx + acdx*cddx*cy - acdx*cddy*cx + 2*acdy*bx*cddx) / d;
+ py = (abdx*acdy*by + abdx*acdy*cy - 2*abdy*acdx*by + abdy*acdy*bx - abdy*acdy*cx -
+ 2*acdx*by*cddy + acdy*bx*cddy + acdy*by*cddx + acdy*cddx*cy - acdy*cddy*cx) / d;
+ bclamp(px, -32767.f, 32767.f);
+ bclamp(py, -32767.f, 32767.f);
+}
+static void smooth_qxy(
+ float ax, float ay, float bx, float by,
+ float cx, float cy, float dx, float dy,
+ float &qx, float &qy)
+{
+ float abdx = bx - ax, abdy = by - ay;
+ float bddx = dx - bx, bddy = dy - by;
+ float cddx = dx - cx, cddy = dy - cy;
+ float d = (2*(abdx*bddy - abdy*bddx - bddx*cddy + bddy*cddx));
+ if( fabsf(d) < 1 ) d = 1;
+ qx = (abdx*bddx*by - abdx*bddx*cy + 2*abdx*bddy*cx - abdy*bddx*bx - abdy*bddx*cx -
+ bddx*bx*cddy + bddx*by*cddx - bddx*cddx*cy - bddx*cddy*cx + 2*bddy*cddx*cx) / d;
+ qy = (abdx*bddy*by + abdx*bddy*cy - 2*abdy*bddx*cy - abdy*bddy*bx + abdy*bddy*cx -
+ 2*bddx*cddy*cy - bddy*bx*cddy + bddy*by*cddx + bddy*cddx*cy + bddy*cddy*cx) / d;
+ bclamp(qx, -32767.f, 32767.f);
+ bclamp(qy, -32767.f, 32767.f);
+}
+
+
+static int convex(float ax,float ay, float bx,float by,
+ float cx,float cy, float dx,float dy)
+{
+ float abdx = bx - ax, abdy = by - ay;
+ float acdx = cx - ax, acdy = cy - ay;
+ float bcdx = cx - bx, bcdy = cy - by;
+ float bddx = dx - bx, bddy = dy - by;
+ float abc = abdx*acdy - abdy*acdx;
+ float bcd = bcdx*bddy - bcdy*bddx;
+ float v = abc * bcd;
+ return !v ? 0 : v>0 ? 1 : -1;
+}
+
+void SketcherCurve::draw_smooth(VFrame *out)
+{
+ VFrame *vpen = new_vpen(out);
+ out->set_pixel_color(color);
+ int n = points.size();
+ if( !n ) return;
+ if( n > 2 ) {
+ SketcherPoint *pt0 = points[0], *pt1 = points[1], *pt2 = points[2];
+ float bx = pt0->x, by = pt0->y;
+ float cx = pt1->x, cy = pt1->y;
+ float dx = pt2->x, dy = pt2->y;
+ float xc = (bx+dx)/2.f, yc = (by+dy)/2.f;
+ float xd = cx - xc, yd = cy - yc;
+ float ax = xc - xd, ay = yc - yd;
+ float sx, sy;
+ for( int pi=0,n2=n-2; pi<n2; ++pi ) {
+ float dx = points[pi+2]->x, dy = points[pi+2]->y;
+ if( convex(ax,ay, bx,by, cx,cy, dx,dy) >= 0 ) {
+ smooth_sxy(ax,ay, bx,by, cx,cy, dx,dy, sx, sy);
+ vpen->draw_smooth(bx,by, sx,sy, cx,cy);
+ }
+ else {
+ float zx = (bx+cx)/2.f, zy = (by+cy)/2.f;
+ smooth_pxy(ax,ay, bx,by, cx,cy, dx,dy, sx,sy);
+ vpen->draw_smooth(bx,by, sx,sy, zx,zy);
+ smooth_qxy(ax,ay, bx,by, cx,cy, dx,dy, sx,sy);
+ vpen->draw_smooth(zx,zy, sx,sy, cx,cy);
+ }
+ ax = bx; ay = by;
+ bx = cx; by = cy;
+ cx = dx; cy = dy;
+ }
+ xc = (ax+cx)/2.f; yc = (ay+cy)/2.f;
+ xd = bx - xc, yd = by - yc;
+ dx = xc - xd, dy = yc - yd;
+ smooth_sxy(ax, ay, bx, by, cx, cy, dx, dy, sx, sy);
+ vpen->draw_smooth(bx, by, sx, sy, cx, cy);
+ }
+ else if( n == 2 ) {
+ SketcherPoint *pt0 = points[0], *pt1 = points[1];
+ vpen->draw_line(pt0->x, pt0->y, pt1->x, pt1->y);
+ }
+ else if( n > 0 ) {
+ SketcherPoint *pt0 = points[0];
+ vpen->draw_pixel(pt0->x, pt0->y);
+ }
+ delete vpen;
+}
+
+int Sketcher::process_realtime(VFrame *input, VFrame *output)
+{
+ this->input = input; this->output = output;
+ w = output->get_w(); h = output->get_h();
+ if( input != output ) output->transfer_from(input);
+
+ load_configuration();
+
+ for( int ci=0, n=config.curves.size(); ci<n; ++ci ) {
+ SketcherCurve *cv = config.curves[ci];
+ int m = cv->points.size();
+ if( !m ) continue;
+ switch( cv->ty ) {
+ case TYP_OFF:
+ break;
+ case TYP_SKETCHER:
+ cv->draw_line(output);
+ break;
+ case TYP_SMOOTH:
+ cv->draw_smooth(output);
+ break;
+ }
+ }
+
+ if( config.drag ) {
+ for( int ci=0, n=config.curves.size(); ci<n; ++ci ) {
+ SketcherCurve *cv = config.curves[ci];
+ for( int pi=0,m=cv->points.size(); pi<m; ++pi )
+ draw_point(output, cv->points[pi], cv->color);
+ }
+ }
+
+ return 0;
+}
+
+void SketcherCurves::dump()
+{
+ for( int i=0; i<size(); ++i ) {
+ SketcherCurve *cv = get(i);
+ printf("Curve %d, id=%d, ty=%s, r=%d, pen=%s, color=%02x%02x%02x\n",
+ i, cv->id, cv_type[cv->ty], cv->radius, cv_pen[cv->pen],
+ (cv->color>>16)&0xff, (cv->color>>8)&0xff, (cv->color>>0)&0xff);
+ cv->points.dump();
+ }
+}
+void SketcherPoints::dump()
+{
+ for( int i=0; i<size(); ++i ) {
+ SketcherPoint *pt = get(i);
+ printf(" Pt %d, id=%d, x=%d, y=%d\n", i, pt->id, pt->x, pt->y);
+ }
+}
+
--- /dev/null
+/*
+ * CINELERRA
+ * Copyright (C) 1997-2014 Adam Williams <broadcast at earthling dot net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+
+
+#ifndef __SKETCHERS_H__
+#define __SKETCHERS_H__
+
+#include "pluginvclient.h"
+
+class Sketcher;
+
+enum { PT_ID, PT_X, PT_Y, PT_SZ };
+enum { CV_ID, CV_TY, CV_RAD, CV_PEN, CV_CLR, CV_SZ };
+enum { TYP_OFF, TYP_SKETCHER, TYP_SMOOTH, TYP_SZ };
+enum { PEN_SQUARE, PEN_PLUS, PEN_SLANT, PEN_XLANT, PEN_SZ };
+
+class SketcherVPen : public VFrame
+{
+public:
+ SketcherVPen(VFrame *vfrm, int n)
+ : VFrame(vfrm->get_data(), -1, vfrm->get_y()-vfrm->get_data(),
+ vfrm->get_u()-vfrm->get_data(), vfrm->get_v()-vfrm->get_data(),
+ vfrm->get_w(), vfrm->get_h(), vfrm->get_color_model(),
+ vfrm->get_bytes_per_line()) {
+ this->vfrm = vfrm; this->n = n;
+ }
+ virtual int draw_pixel(int x, int y) = 0;
+ VFrame *vfrm;
+ int n;
+};
+
+class SketcherPenSquare : public SketcherVPen
+{
+public:
+ SketcherPenSquare(VFrame *vfrm, int n) : SketcherVPen(vfrm, n) {}
+ int draw_pixel(int x, int y);
+};
+class SketcherPenPlus : public SketcherVPen
+{
+public:
+ SketcherPenPlus(VFrame *vfrm, int n) : SketcherVPen(vfrm, n) {}
+ int draw_pixel(int x, int y);
+};
+class SketcherPenSlant : public SketcherVPen
+{
+public:
+ SketcherPenSlant(VFrame *vfrm, int n) : SketcherVPen(vfrm, n) {}
+ int draw_pixel(int x, int y);
+};
+class SketcherPenXlant : public SketcherVPen
+{
+public:
+ SketcherPenXlant(VFrame *vfrm, int n) : SketcherVPen(vfrm, n) {}
+ int draw_pixel(int x, int y);
+};
+
+
+class SketcherPoint
+{
+public:
+ int id;
+ int x, y;
+
+ void init(int id, int x, int y);
+ SketcherPoint(int id, int x, int y);
+ SketcherPoint(int id=-1);
+ SketcherPoint(SketcherPoint &pt);
+ ~SketcherPoint();
+ int equivalent(SketcherPoint &that);
+ void copy_from(SketcherPoint &that);
+ void save_data(FileXML &output);
+ void read_data(FileXML &input);
+};
+class SketcherPoints : public ArrayList<SketcherPoint *>
+{
+public:
+ SketcherPoints() {}
+ ~SketcherPoints() { remove_all_objects(); }
+ void dump();
+};
+
+#define cv_type SketcherCurve::types
+#define cv_pen SketcherCurve::pens
+
+class SketcherCurve
+{
+public:
+ int id, ty, radius, pen, color;
+ static const char *types[TYP_SZ];
+ static const char *pens[PEN_SZ];
+
+ SketcherPoints points;
+
+ void init(int id, int ty, int radius, int pen, int color);
+ SketcherCurve(int id, int ty, int radius, int pen, int color);
+ SketcherCurve(int id=-1);
+ ~SketcherCurve();
+ SketcherCurve(SketcherCurve &cv);
+ int equivalent(SketcherCurve &that);
+ void copy_from(SketcherCurve &that);
+ void save_data(FileXML &output);
+ void read_data(FileXML &input);
+ VFrame *new_vpen(VFrame *out);
+ void draw_line(VFrame *out);
+ void draw_smooth(VFrame *out);
+};
+class SketcherCurves : public ArrayList<SketcherCurve *>
+{
+public:
+ SketcherCurves() {}
+ ~SketcherCurves() { remove_all_objects(); }
+ void dump();
+};
+
+class SketcherConfig
+{
+public:
+ SketcherConfig();
+ ~SketcherConfig();
+
+ SketcherCurves curves;
+ int equivalent(SketcherConfig &that);
+ void copy_from(SketcherConfig &that);
+ void interpolate(SketcherConfig &prev, SketcherConfig &next,
+ long prev_frame, long next_frame, long current_frame);
+ void limits();
+
+ int drag;
+ int cv_selected, pt_selected;
+};
+
+class Sketcher : public PluginVClient
+{
+public:
+ Sketcher(PluginServer *server);
+ ~Sketcher();
+// required for all realtime plugins
+ PLUGIN_CLASS_MEMBERS2(SketcherConfig)
+ int is_realtime();
+ void update_gui();
+ void save_data(KeyFrame *keyframe);
+ void read_data(KeyFrame *keyframe);
+ int new_curve(int ty, int radius, int pen, int color);
+ int new_curve();
+ int new_point(SketcherCurve *cv, int x, int y);
+ int new_point();
+ int process_realtime(VFrame *input, VFrame *output);
+ static void draw_point(VFrame *vfrm, SketcherPoint *pt, int color, int d);
+ void draw_point(VFrame *vfrm, SketcherPoint *pt, int color);
+
+ VFrame *input, *output;
+ int w, h, color_model, bpp, comp;
+ int is_yuv, is_float;
+};
+
+#endif
--- /dev/null
+/*
+ * CINELERRA
+ * Copyright (C) 2014 Adam Williams <broadcast at earthling dot net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "automation.h"
+#include "bcdisplayinfo.h"
+#include "clip.h"
+#include "sketcher.h"
+#include "sketcherwindow.h"
+#include "cstrdup.h"
+#include "cwindow.h"
+#include "cwindowgui.h"
+#include "edl.h"
+#include "edlsession.h"
+#include "keys.h"
+#include "language.h"
+#include "mainerror.h"
+#include "mwindow.h"
+#include "plugin.h"
+#include "pluginserver.h"
+#include "theme.h"
+#include "track.h"
+
+#define COLOR_W 30
+#define COLOR_H 30
+
+const char *SketcherCurve::types[] = {
+ N_("off"),
+ N_("line"),
+ N_("smooth"),
+};
+const char *SketcherCurve::pens[] = {
+ N_("box"),
+ N_("+"),
+ N_("/"),
+ N_("X"),
+};
+
+SketcherCurveTypeItem::SketcherCurveTypeItem(int ty)
+ : BC_MenuItem(_(cv_type[ty]))
+{
+ this->ty = ty;
+}
+int SketcherCurveTypeItem::handle_event()
+{
+ SketcherCurveType *popup = (SketcherCurveType*)get_popup_menu();
+ popup->update(ty);
+ SketcherWindow *gui = popup->gui;
+ SketcherConfig &config = gui->plugin->config;
+ int ci = config.cv_selected;
+ if( ci >= 0 && ci < config.curves.size() ) {
+ SketcherCurve *cv = config.curves[ci];
+ cv->ty = ty;
+ gui->curve_list->update(ci);
+ gui->send_configure_change();
+ }
+ return 1;
+}
+
+SketcherCurveType::SketcherCurveType(SketcherWindow *gui, int x, int y, int ty)
+ : BC_PopupMenu(x,y,64,_(cv_type[ty]))
+{
+ this->gui = gui;
+}
+void SketcherCurveType::create_objects()
+{
+ int n = sizeof(cv_type)/sizeof(cv_type[0]);
+ for( int ty=0; ty<n; ++ty )
+ add_item(new SketcherCurveTypeItem(ty));
+}
+void SketcherCurveType::update(int ty)
+{
+ set_text(_(cv_type[ty]));
+}
+
+
+SketcherCurvePenItem::SketcherCurvePenItem(int pen)
+ : BC_MenuItem(_(cv_pen[pen]))
+{
+ this->pen = pen;
+}
+int SketcherCurvePenItem::handle_event()
+{
+ SketcherCurvePen *popup = (SketcherCurvePen*)get_popup_menu();
+ popup->update(pen);
+ SketcherWindow *gui = popup->gui;
+ SketcherConfig &config = gui->plugin->config;
+ int ci = config.cv_selected;
+ if( ci >= 0 && ci < config.curves.size() ) {
+ SketcherCurve *cv = config.curves[ci];
+ cv->pen = pen;
+ gui->curve_list->update(ci);
+ gui->send_configure_change();
+ }
+ return 1;
+}
+
+SketcherCurvePen::SketcherCurvePen(SketcherWindow *gui, int x, int y, int pen)
+ : BC_PopupMenu(x,y,72,_(cv_pen[pen]))
+{
+ this->gui = gui;
+}
+void SketcherCurvePen::create_objects()
+{
+ int n = sizeof(cv_pen)/sizeof(cv_pen[0]);
+ for( int pen=0; pen<n; ++pen )
+ add_item(new SketcherCurvePenItem(pen));
+}
+void SketcherCurvePen::update(int pen)
+{
+ set_text(_(cv_pen[pen]));
+}
+
+
+SketcherCurveColor::SketcherCurveColor(SketcherWindow *gui, int x, int y, int w)
+ : BC_Button(x, y, w, vframes)
+{
+ this->gui = gui;
+ this->color = BLACK;
+ for( int i=0; i<3; ++i ) {
+ vframes[i] = new VFrame(w, w, BC_RGBA8888);
+ vframes[i]->clear_frame();
+ }
+}
+
+SketcherCurveColor::~SketcherCurveColor()
+{
+ for( int i=0; i<3; ++i )
+ delete vframes[i];
+}
+
+void SketcherCurveColor::set_color(int color)
+{
+ this->color = color;
+ int r = (color>>16) & 0xff;
+ int g = (color>>8) & 0xff;
+ int b = (color>>0) & 0xff;
+ for( int i=0; i<3; ++i ) {
+ VFrame *vframe = vframes[i];
+ int ww = vframe->get_w(), hh = vframe->get_h();
+ int cx = (ww+1)/2, cy = hh/2;
+ double cc = (cx*cx + cy*cy) / 4.;
+ uint8_t *bp = vframe->get_data(), *dp = bp;
+ uint8_t *ep = dp + vframe->get_data_size();
+ int rr = r, gg = g, bb = b;
+ int bpl = vframe->get_bytes_per_line();
+ switch( i ) {
+ case BUTTON_UP:
+ break;
+ case BUTTON_UPHI:
+ if( (rr+=48) > 0xff ) rr = 0xff;
+ if( (gg+=48) > 0xff ) gg = 0xff;
+ if( (bb+=48) > 0xff ) bb = 0xff;
+ break;
+ case BUTTON_DOWNHI:
+ if( (rr-=48) < 0x00 ) rr = 0x00;
+ if( (gg-=48) < 0x00 ) gg = 0x00;
+ if( (bb-=48) < 0x00 ) bb = 0x00;
+ break;
+ }
+ while( dp < ep ) {
+ int yy = (dp-bp) / bpl, xx = ((dp-bp) % bpl) >> 2;
+ int dy = cy - yy, dx = cx - xx;
+ double s = dx*dx + dy*dy - cc;
+ double ss = s < 0 ? 1 : s >= cc ? 0 : 1 - s/cc;
+ int aa = ss * 0xff;
+ *dp++ = rr; *dp++ = gg; *dp++ = bb; *dp++ = aa;
+ }
+ }
+ set_images(vframes);
+}
+
+void SketcherCurveColor::update_gui(int color)
+{
+ set_color(color);
+ draw_face();
+}
+
+int SketcherCurveColor::handle_event()
+{
+ gui->start_color_thread(this);
+ return 1;
+}
+
+SketcherCurveColorPicker::SketcherCurveColorPicker(SketcherWindow *gui, SketcherCurveColor *color_button)
+ : ColorPicker(0, _("Color"))
+{
+ this->gui = gui;
+ this->color_button = color_button;
+ this->color = 0;
+ color_update = new SketcherCurveColorThread(this);
+}
+
+SketcherCurveColorPicker::~SketcherCurveColorPicker()
+{
+ delete color_update;
+}
+
+void SketcherCurveColorPicker::start(int color)
+{
+ start_window(color, 0, 1);
+ color_update->start();
+}
+
+void SketcherCurveColorPicker::handle_done_event(int result)
+{
+ color_update->stop();
+ gui->lock_window("SketcherCurveColorPicker::handle_done_event");
+ if( result ) color = orig_color;
+ color_button->update_gui(color);
+ gui->unlock_window();
+ SketcherConfig &config = gui->plugin->config;
+ int ci = config.cv_selected;
+ if( ci >= 0 && ci < config.curves.size() ) {
+ SketcherCurve *cv = config.curves[ci];
+ cv->color = color;
+ gui->curve_list->update(ci);
+ gui->send_configure_change();
+ }
+}
+
+int SketcherCurveColorPicker::handle_new_color(int color, int alpha)
+{
+ this->color = color;
+ color_update->update_lock->unlock();
+ return 1;
+}
+
+void SketcherCurveColorPicker::update_gui()
+{
+ gui->lock_window("SketcherCurveColorPicker::update_gui");
+ color_button->update_gui(color);
+ SketcherConfig &config = gui->plugin->config;
+ int ci = config.cv_selected;
+ if( ci >= 0 ) {
+ SketcherCurve *cv = config.curves[ci];
+ cv->color = color;
+ gui->curve_list->update(ci);
+ gui->send_configure_change();
+ }
+ gui->unlock_window();
+}
+
+SketcherCurveColorThread::SketcherCurveColorThread(SketcherCurveColorPicker *color_picker)
+ : Thread(1, 0, 0)
+{
+ this->color_picker = color_picker;
+ this->update_lock = new Condition(0,"SketcherCurveColorThread::update_lock");
+ done = 1;
+}
+
+SketcherCurveColorThread::~SketcherCurveColorThread()
+{
+ stop();
+ delete update_lock;
+}
+
+void SketcherCurveColorThread::start()
+{
+ if( done ) {
+ done = 0;
+ Thread::start();
+ }
+}
+
+void SketcherCurveColorThread::stop()
+{
+ if( !done ) {
+ done = 1;
+ update_lock->unlock();
+ join();
+ }
+}
+
+void SketcherCurveColorThread::run()
+{
+ while( !done ) {
+ update_lock->lock("SketcherCurveColorThread::run");
+ if( done ) break;
+ color_picker->update_gui();
+ }
+}
+
+
+SketcherNum::SketcherNum(SketcherWindow *gui, int x, int y, int output,
+ int mn, int mx)
+ : BC_TumbleTextBox(gui, output, mn, mx, x, y, 64)
+{
+ this->gui = gui;
+ set_increment(1);
+}
+
+SketcherNum::~SketcherNum()
+{
+}
+
+int SketcherPointX::handle_event()
+{
+ if( !SketcherNum::handle_event() ) return 0;
+ SketcherConfig &config = gui->plugin->config;
+ int ci = config.cv_selected;
+ if( ci >= 0 && ci < config.curves.size() ) {
+ SketcherCurve *cv = config.curves[ci];
+ SketcherPointList *point_list = gui->point_list;
+ int hot_point = point_list->get_selection_number(0, 0);
+ SketcherPoints &points = cv->points;
+ if( hot_point >= 0 && hot_point < points.size() ) {
+ int v = atoi(get_text());
+ points[hot_point]->x = v;
+ point_list->set_point(hot_point, PT_X, v);
+ point_list->update_list(hot_point);
+ gui->send_configure_change();
+ }
+ }
+ return 1;
+}
+int SketcherPointY::handle_event()
+{
+ if( !SketcherNum::handle_event() ) return 0;
+ SketcherConfig &config = gui->plugin->config;
+ int ci = config.cv_selected;
+ if( ci >= 0 && ci < config.curves.size() ) {
+ SketcherCurve *cv = config.curves[ci];
+ SketcherPointList *point_list = gui->point_list;
+ int hot_point = point_list->get_selection_number(0, 0);
+ SketcherPoints &points = cv->points;
+ if( hot_point >= 0 && hot_point < points.size() ) {
+ int v = atoi(get_text());
+ points[hot_point]->y = v;
+ point_list->set_point(hot_point, PT_Y, v);
+ point_list->update_list(hot_point);
+ gui->send_configure_change();
+ }
+ }
+ return 1;
+}
+
+int SketcherCurveRadius::handle_event()
+{
+ if( !SketcherNum::handle_event() ) return 0;
+ SketcherConfig &config = gui->plugin->config;
+ int ci = config.cv_selected;
+ if( ci >= 0 && ci < config.curves.size() ) {
+ SketcherCurve *cv = config.curves[ci];
+ int v = atoi(get_text());
+ cv->radius = v;
+ gui->curve_list->update(ci);
+ gui->send_configure_change();
+ }
+ return 1;
+}
+
+
+SketcherWindow::SketcherWindow(Sketcher *plugin)
+ : PluginClientWindow(plugin, 380, 580, 380, 580, 0)
+{
+ this->plugin = plugin;
+ this->title_type = 0; this->curve_type = 0;
+ this->title_pen = 0; this->curve_pen = 0;
+ this->title_color = 0; this->curve_color = 0;
+ this->color_picker = 0; this->new_points = 0;
+ this->new_curve = 0; this->del_curve = 0;
+ this->curve_up = 0; this->curve_dn = 0;
+ this->title_x = 0; this->point_x = 0;
+ this->title_y = 0; this->point_y = 0;
+ this->new_point = 0; this->del_point = 0;
+ this->point_up = 0; this->point_dn = 0;
+ this->drag = 0; this->dragging = 0;
+ this->last_x = 0; this->last_y = 0;
+ this->point_list = 0; this->pending_config = 0;
+}
+
+SketcherWindow::~SketcherWindow()
+{
+ delete curve_radius;
+ delete point_x;
+ delete point_y;
+ delete color_picker;
+}
+
+void SketcherWindow::create_objects()
+{
+ int x = 10, y = 10, x1, y1;
+ int margin = plugin->get_theme()->widget_border;
+ BC_Title *title;
+ int ci = plugin->config.cv_selected;
+ if( ci < 0 || ci >= plugin->config.curves.size() )
+ ci = plugin->new_curve(0, 1, 0, BLACK);
+ SketcherCurve *cv = plugin->config.curves[ci];
+ add_subwindow(reset_curves = new SketcherResetCurves(this, plugin, x1=x, y+3));
+ x1 += reset_curves->get_w() + 2*margin;
+ const char *curve_text = _("Curve");
+ add_subwindow(title_radius = new BC_Title(x1, y, _("Width:")));
+ x1 += title_radius->get_w() + margin;
+ curve_radius = new SketcherCurveRadius(this, x1, y, cv->radius);
+ curve_radius->create_objects();
+ y += reset_curves->get_h() + 2*margin;
+ x1 = get_w()-x - BC_Title::calculate_w(this, curve_text, LARGEFONT);
+ y1 = y-margin - BC_Title::calculate_h(this, curve_text, LARGEFONT);
+ add_subwindow(title = new BC_Title(x1, y1, curve_text, LARGEFONT,
+ get_resources()->menu_highlighted_fontcolor));
+ add_subwindow(curve_list = new SketcherCurveList(this, plugin, x, y));
+ y += curve_list->get_h() + margin;
+ add_subwindow(title_type = new BC_Title(x, y, _("Type:")));
+ x1 = x + title_type->get_w() + margin;
+ add_subwindow(curve_type = new SketcherCurveType(this, x1, y, cv->ty));
+ curve_type->create_objects();
+ x1 += curve_type->get_w() + margin;
+ add_subwindow(new_curve = new SketcherNewCurve(this, plugin, x1, y));
+ x1 += new_curve->get_w() + margin;
+ add_subwindow(curve_up = new SketcherCurveUp(this, x1, y));
+ x1 += curve_up->get_w() + 2*margin;
+ add_subwindow(title_color = new BC_Title(x1, y, _("Color:")));
+ y += curve_type->get_h() + margin;
+
+ add_subwindow(title_pen = new BC_Title(x, y, _("Pen:")));
+ x1 = x + title_pen->get_w() + margin;
+ add_subwindow(curve_pen = new SketcherCurvePen(this, x1, y, cv->pen));
+ curve_pen->create_objects();
+ x1 += curve_pen->get_w() + margin;
+ add_subwindow(del_curve = new SketcherDelCurve(this, plugin, x1, y));
+ x1 += del_curve->get_w() + margin;
+ add_subwindow(curve_dn = new SketcherCurveDn(this, x1, y));
+ x1 += curve_dn->get_w() + 2*margin;
+ add_subwindow(curve_color = new SketcherCurveColor(this, x1, y, COLOR_W));
+ curve_color->create_objects();
+ curve_color->set_color(cv->color);
+ curve_color->draw_face();
+ y += COLOR_H + margin;
+ curve_list->update(ci);
+
+ BC_Bar *bar;
+ add_subwindow(bar = new BC_Bar(x, y, get_w()-2*x));
+ y += bar->get_h() + 2*margin;
+
+ int pi = plugin->config.pt_selected;
+ SketcherPoint *pt = pi >= 0 && pi < cv->points.size() ? cv->points[pi] : 0;
+ add_subwindow(reset_points = new SketcherResetPoints(this, plugin, x1=x, y+3));
+ x1 += reset_points->get_w() + 2*margin;
+ add_subwindow(drag = new SketcherDrag(this, x1, y));
+ y += drag->get_h() + margin;
+ if( plugin->config.drag ) {
+ if( !grab(plugin->server->mwindow->cwindow->gui) )
+ eprintf("drag enabled, but compositor already grabbed\n");
+ }
+ const char *point_text = _("Point");
+ x1 = get_w()-x - BC_Title::calculate_w(this, point_text, LARGEFONT);
+ y1 = y-margin - BC_Title::calculate_h(this, point_text, LARGEFONT);
+ add_subwindow(title = new BC_Title(x1, y1, point_text, LARGEFONT,
+ get_resources()->menu_highlighted_fontcolor));
+ add_subwindow(point_list = new SketcherPointList(this, plugin, x, y));
+
+ y += point_list->get_h() + margin;
+ add_subwindow(title_x = new BC_Title(x, y, _("X:")));
+ x1 = x + title_x->get_w() + margin;
+ point_x = new SketcherPointX(this, x1, y, !pt ? 0.f : pt->x);
+ point_x->create_objects();
+ x1 += point_x->get_w() + 2*margin;
+ add_subwindow(new_point = new SketcherNewPoint(this, plugin, x1, y));
+ x1 += new_point->get_w() + margin;
+ add_subwindow(point_up = new SketcherPointUp(this, x1, y));
+ y += point_x->get_h() + margin;
+ add_subwindow(title_y = new BC_Title(x, y, _("Y:")));
+ x1 = x + title_y->get_w() + margin;
+ point_y = new SketcherPointY(this, x1, y, !pt ? 0.f : pt->y);
+ point_y->create_objects();
+ x1 += point_y->get_w() + 2*margin;
+ add_subwindow(del_point = new SketcherDelPoint(this, plugin, x1, y));
+ x1 += del_point->get_w() + margin;
+ add_subwindow(point_dn = new SketcherPointDn(this, x1, y));
+ y += point_y->get_h() + margin + 10;
+ point_list->update(pi);
+
+ add_subwindow(notes0 = new BC_Title(x, y,
+ _("\n"
+ "LMB=\n"
+ "Alt+LMB=\n"
+ "MMB=\n"
+ "DEL=\n")));
+ add_subwindow(notes1 = new BC_Title(x+80, y,
+ _(" No Shift\n"
+ "select point\n"
+ "drag curve\n"
+ "next curve type\n"
+ "deletes point\n")));
+ add_subwindow(notes2 = new BC_Title(x+200, y,
+ _(" Shift\n"
+ "append new points\n"
+ "drag image\n"
+ "append new curve\n"
+ "delete curve\n")));
+ show_window(1);
+}
+
+void SketcherWindow::send_configure_change()
+{
+ pending_config = 0;
+ plugin->send_configure_change();
+}
+
+int SketcherWindow::grab_event(XEvent *event)
+{
+ int ret = do_grab_event(event);
+ if( pending_config && !grab_event_count() )
+ send_configure_change();
+ return ret;
+}
+
+int SketcherWindow::do_grab_event(XEvent *event)
+{
+ switch( event->type ) {
+ case ButtonPress: break;
+ case ButtonRelease: break;
+ case MotionNotify: break;
+ case KeyPress:
+ if( keysym_lookup(event) > 0 ) {
+ switch( get_keysym() ) {
+ case XK_Delete:
+ return (event->xkey.state & ShiftMask) ?
+ del_curve->handle_event() :
+ del_point->handle_event() ;
+ }
+ } // fall thru
+ default:
+ return 0;
+ }
+
+ MWindow *mwindow = plugin->server->mwindow;
+ CWindowGUI *cwindow_gui = mwindow->cwindow->gui;
+ CWindowCanvas *canvas = cwindow_gui->canvas;
+ int cx, cy; cwindow_gui->get_relative_cursor(cx, cy);
+ cx -= mwindow->theme->ccanvas_x;
+ cy -= mwindow->theme->ccanvas_y;
+
+ if( !dragging ) {
+ if( cx < 0 || cx >= mwindow->theme->ccanvas_w ||
+ cy < 0 || cy >= mwindow->theme->ccanvas_h )
+ return 0;
+ }
+
+ switch( event->type ) {
+ case ButtonPress:
+ if( dragging ) return 0;
+ dragging = event->xbutton.state & Mod1Mask ? -1 : 1; // alt_down
+ break;
+ case ButtonRelease:
+ case MotionNotify:
+ if( !dragging ) return 0;
+ break;
+ default:
+ return 0;
+ }
+
+
+ int ci = plugin->config.cv_selected;
+ if( ci < 0 || ci >= plugin->config.curves.size() )
+ return 1;
+
+ float cursor_x = cx, cursor_y = cy;
+ canvas->canvas_to_output(mwindow->edl, 0, cursor_x, cursor_y);
+ int64_t position = plugin->get_source_position();
+ float projector_x, projector_y, projector_z;
+ Track *track = plugin->server->plugin->track;
+ int track_w = track->track_w, track_h = track->track_h;
+ track->automation->get_projector(
+ &projector_x, &projector_y, &projector_z,
+ position, PLAY_FORWARD);
+ projector_x += mwindow->edl->session->output_w / 2;
+ projector_y += mwindow->edl->session->output_h / 2;
+ float output_x = (cursor_x - projector_x) / projector_z + track_w / 2;
+ float output_y = (cursor_y - projector_y) / projector_z + track_h / 2;
+ SketcherCurve *cv = plugin->config.curves[ci];
+ SketcherPoints &points = cv->points;
+ int state = event->xmotion.state;
+
+ switch( event->type ) {
+ case ButtonPress: {
+ if( dragging < 0 ) break;
+ int hot_point = -1;
+ int button_no = event->xbutton.button;
+ if( button_no == LEFT_BUTTON ) {
+// create new point string
+ if( (state & ShiftMask) ) {
+ ++new_points;
+ hot_point = plugin->new_point(cv, output_x, output_y);
+ point_list->update(hot_point);
+ }
+ else {
+// select point
+ int sz = points.size();
+ int last_point = hot_point;
+ if( sz > 0 ) {
+ SketcherPoint *pt = points[hot_point=0];
+ double dist = DISTANCE(output_x,output_y, pt->x,pt->y);
+ for( int i=1; i<sz; ++i ) {
+ pt = points[i];
+ double d = DISTANCE(output_x,output_y, pt->x,pt->y);
+ if( d >= dist ) continue;
+ dist = d; hot_point = i;
+ }
+ pt = points[hot_point];
+ float px = (pt->x - track_w / 2) * projector_z + projector_x;
+ float py = (pt->y - track_h / 2) * projector_z + projector_y;
+ dist = DISTANCE(px, py, cursor_x,cursor_y);
+ if( dist >= HANDLE_W ) hot_point = -1;
+ }
+ if( hot_point != last_point ) {
+ SketcherPoint *pt = 0;
+ if( hot_point >= 0 && hot_point < sz ) {
+ pt = points[hot_point];
+ point_list->set_point(hot_point, PT_X, pt->x = output_x);
+ point_list->set_point(hot_point, PT_Y, pt->y = output_y);
+ }
+ point_list->update_list(hot_point);
+ point_x->update(pt ? pt->x : 0.f);
+ point_y->update(pt ? pt->y : 0.f);
+ }
+ }
+ }
+ else if( button_no == MIDDLE_BUTTON ) {
+ if( (state & ShiftMask) ) {
+ int ci = plugin->new_curve(cv->ty, cv->radius, cv->pen, cv->color);
+ curve_list->update(ci);
+ point_list->update(-1);
+ }
+ else {
+ int ty = cv->ty + 1;
+ if( ty >= TYP_SZ ) ty = 0;
+ cv->ty = ty;
+ curve_type->update(ty);
+ curve_list->update(ci);
+ }
+ }
+ break; }
+ case MotionNotify: {
+ int hot_point = point_list->get_selection_number(0, 0);
+ if( dragging < 0 ) {
+ SketcherCurves &curves = plugin->config.curves;
+ int dx = round(output_x - last_x);
+ int dy = round(output_y - last_y);
+ int mnc = (state & ShiftMask) || ci<0 ? 0 : ci;
+ int mxc = (state & ShiftMask) ? curves.size() : ci+1;
+ for( int i=mnc; i<mxc; ++i ) {
+ SketcherCurve *crv = plugin->config.curves[i];
+ int pts = crv->points.size();
+ for( int k=0; k<pts; ++k ) {
+ SketcherPoint *pt = crv->points[k];
+ pt->x += dx; pt->y += dy;
+ }
+ }
+ SketcherPoint *pt = hot_point >= 0 && hot_point < points.size() ?
+ points[hot_point] : 0;
+ point_x->update(pt ? pt->x : 0.f);
+ point_y->update(pt ? pt->y : 0.f);
+ point_list->update(hot_point);
+ break;
+ }
+ if( (state & Button1Mask) ) {
+ SketcherPoint *pt = hot_point >= 0 && hot_point < points.size() ?
+ points[hot_point] : 0;
+ if( pt && pt->x == output_x && pt->y == output_y ) break;
+ if( new_points ) {
+ if( pt ) {
+ float frac_w = DISTANCE(pt->x, pt->y, output_x, output_y) / get_w();
+ if( frac_w < 0.01 ) break; // 1 percent w
+ }
+ if( (state & ShiftMask) ) {
+ ++new_points;
+ hot_point = plugin->new_point(cv, output_x, output_y);
+ point_list->update(hot_point);
+ }
+ }
+ else if( pt ) {
+ point_list->set_point(hot_point, PT_X, pt->x = output_x);
+ point_list->set_point(hot_point, PT_Y, pt->y = output_y);
+ point_list->update_list(hot_point);
+ point_x->update(pt->x);
+ point_y->update(pt->y);
+ }
+ }
+ break; }
+ case ButtonRelease: {
+ new_points = 0;
+ dragging = 0;
+ break; }
+ }
+
+ last_x = output_x; last_y = output_y;
+ pending_config = 1;
+ return 1;
+}
+
+int SketcherWindow::keypress_event()
+{
+ int key = get_keypress();
+ switch( key ) {
+ case DELETE: return shift_down() ?
+ del_curve->handle_event() :
+ del_point->handle_event() ;
+ }
+ return 0;
+}
+
+void SketcherWindow::done_event(int result)
+{
+ ungrab(client->server->mwindow->cwindow->gui);
+}
+
+void SketcherWindow::start_color_thread(SketcherCurveColor *color_button)
+{
+ unlock_window();
+ delete color_picker;
+ color_picker = new SketcherCurveColorPicker(this, color_button);
+ int color = BLACK, ci = plugin->config.cv_selected;
+ if( ci >= 0 && ci < plugin->config.curves.size() ) {
+ SketcherCurve *cv = plugin->config.curves[ci];
+ color = cv->color;
+ }
+ color_picker->start(color);
+ lock_window("SketcherWindow::start_color_thread");
+}
+
+
+SketcherCurveList::SketcherCurveList(SketcherWindow *gui, Sketcher *plugin, int x, int y)
+ : BC_ListBox(x, y, 360, 130, LISTBOX_TEXT)
+{
+ this->gui = gui;
+ this->plugin = plugin;
+ titles[CV_ID] = _("id"); widths[CV_ID] = 64;
+ titles[CV_TY] = _("type"); widths[CV_TY] = 64;
+ titles[CV_RAD] = _("radius"); widths[CV_RAD] = 64;
+ titles[CV_PEN] = _("pen"); widths[CV_PEN] = 64;
+ titles[CV_CLR] = _("color"); widths[CV_CLR] = 64;
+}
+SketcherCurveList::~SketcherCurveList()
+{
+ clear();
+}
+void SketcherCurveList::clear()
+{
+ for( int i=CV_SZ; --i>=0; )
+ cols[i].remove_all_objects();
+}
+
+int SketcherCurveList::column_resize_event()
+{
+ for( int i=CV_SZ; --i>=0; )
+ widths[i] = get_column_width(i);
+ return 1;
+}
+
+int SketcherCurveList::handle_event()
+{
+ int ci = get_selection_number(0, 0);
+ set_selected(ci);
+ gui->point_list->update(0);
+ gui->send_configure_change();
+ return 1;
+}
+
+int SketcherCurveList::selection_changed()
+{
+ handle_event();
+ return 1;
+}
+
+void SketcherCurveList::set_selected(int k)
+{
+ int ci = -1;
+ if( k >= 0 && k < plugin->config.curves.size() ) {
+ SketcherCurve *cv = plugin->config.curves[k];
+ gui->curve_type->update(cv->ty);
+ gui->curve_radius->update(cv->radius);
+ gui->curve_pen->update(cv->pen);
+ gui->curve_color->update_gui(cv->color);
+ ci = k;
+ }
+ plugin->config.cv_selected = ci;
+ update_selection(&cols[0], ci);
+ update_list(-1);
+}
+
+void SketcherCurveList::update_list(int k)
+{
+ int xpos = get_xposition(), ypos = get_yposition();
+ if( k < 0 ) k = get_selection_number(0, 0);
+ update_selection(&cols[0], k);
+ BC_ListBox::update(&cols[0], &titles[0],&widths[0],CV_SZ, xpos,ypos,k);
+ center_selection();
+}
+
+void SketcherCurveList::update(int k)
+{
+ clear();
+ SketcherCurves &curves = plugin->config.curves;
+ int sz = curves.size();
+ for( int i=0; i<sz; ++i ) {
+ SketcherCurve *cv = curves[i];
+ char itxt[BCSTRLEN]; sprintf(itxt,"%d", cv->id);
+ char ttxt[BCSTRLEN]; sprintf(ttxt,"%s", cv_type[cv->ty]);
+ char rtxt[BCSTRLEN]; sprintf(rtxt,"%d", cv->radius);
+ char ptxt[BCSTRLEN]; sprintf(ptxt,"%s", cv_pen[cv->pen]);
+ int color = cv->color;
+ int r = (color>>16)&0xff, g = (color>>8)&0xff, b = (color>>0)&0xff;
+ char ctxt[BCSTRLEN]; sprintf(ctxt,"#%02x%02x%02x", r, g, b);
+ add_curve(itxt, ttxt, rtxt, ptxt, ctxt);
+ }
+ set_selected(k);
+}
+
+void SketcherCurveList::add_curve(const char *id, const char *type,
+ const char *radius, const char *pen, const char *color)
+{
+ cols[CV_ID].append(new BC_ListBoxItem(id));
+ cols[CV_TY].append(new BC_ListBoxItem(type));
+ cols[CV_RAD].append(new BC_ListBoxItem(radius));
+ cols[CV_PEN].append(new BC_ListBoxItem(pen));
+ cols[CV_CLR].append(new BC_ListBoxItem(color));
+}
+
+SketcherNewCurve::SketcherNewCurve(SketcherWindow *gui, Sketcher *plugin, int x, int y)
+ : BC_GenericButton(x, y, 64, _("New"))
+{
+ this->gui = gui;
+ this->plugin = plugin;
+}
+SketcherNewCurve::~SketcherNewCurve()
+{
+}
+int SketcherNewCurve::handle_event()
+{
+ int ty = 1, pen = 0, color = 0, radius = 1;
+ int ci = plugin->config.cv_selected;
+ if( ci >= 0 && ci < plugin->config.curves.size() ) {
+ SketcherCurve *cv = plugin->config.curves[ci];
+ ty = cv->ty; pen = cv->pen;
+ color = cv->color; radius = cv->radius;
+ }
+ int k = plugin->new_curve(ty, radius, pen, color);
+ gui->curve_list->update(k);
+ gui->point_list->update(-1);
+ gui->send_configure_change();
+ return 1;
+}
+
+SketcherDelCurve::SketcherDelCurve(SketcherWindow *gui, Sketcher *plugin, int x, int y)
+ : BC_GenericButton(x, y, 64, C_("Del"))
+{
+ this->gui = gui;
+ this->plugin = plugin;
+}
+SketcherDelCurve::~SketcherDelCurve()
+{
+}
+int SketcherDelCurve::handle_event()
+{
+ int hot_curve = gui->curve_list->get_selection_number(0, 0);
+ SketcherCurves &curves = plugin->config.curves;
+ if( hot_curve >= 0 && hot_curve < curves.size() ) {
+ curves.remove_object_number(hot_curve);
+ if( --hot_curve < 0 )
+ hot_curve = plugin->new_curve(0, 1, 0, BLACK);
+ gui->curve_list->update(hot_curve);
+ gui->point_list->update(-1);
+ gui->send_configure_change();
+ }
+ return 1;
+}
+
+SketcherCurveUp::SketcherCurveUp(SketcherWindow *gui, int x, int y)
+ : BC_GenericButton(x, y, _("Up"))
+{
+ this->gui = gui;
+}
+SketcherCurveUp::~SketcherCurveUp()
+{
+}
+
+int SketcherCurveUp::handle_event()
+{
+ SketcherCurves &curves = gui->plugin->config.curves;
+ int hot_curve = gui->curve_list->get_selection_number(0, 0);
+
+ if( hot_curve > 0 && hot_curve < curves.size() ) {
+ SketcherCurve *&cv0 = curves[hot_curve];
+ SketcherCurve *&cv1 = curves[--hot_curve];
+ SketcherCurve *t = cv0; cv0 = cv1; cv1 = t;
+ gui->curve_list->update(hot_curve);
+ }
+ gui->send_configure_change();
+ return 1;
+}
+
+SketcherCurveDn::SketcherCurveDn(SketcherWindow *gui, int x, int y)
+ : BC_GenericButton(x, y, _("Dn"))
+{
+ this->gui = gui;
+}
+SketcherCurveDn::~SketcherCurveDn()
+{
+}
+
+int SketcherCurveDn::handle_event()
+{
+ SketcherCurves &curves = gui->plugin->config.curves;
+ int hot_curve = gui->curve_list->get_selection_number(0, 0);
+
+ if( hot_curve >= 0 && hot_curve < curves.size()-1 ) {
+ SketcherCurve *&cv0 = curves[hot_curve];
+ SketcherCurve *&cv1 = curves[++hot_curve];
+ SketcherCurve *t = cv0; cv0 = cv1; cv1 = t;
+ gui->curve_list->update(hot_curve);
+ }
+ gui->send_configure_change();
+ return 1;
+}
+
+
+SketcherPointList::SketcherPointList(SketcherWindow *gui, Sketcher *plugin, int x, int y)
+ : BC_ListBox(x, y, 360, 130, LISTBOX_TEXT)
+{
+ this->gui = gui;
+ this->plugin = plugin;
+ titles[PT_X] = _("X"); widths[PT_X] = 90;
+ titles[PT_Y] = _("Y"); widths[PT_Y] = 90;
+ titles[PT_ID] = _("ID"); widths[PT_ID] = 50;
+}
+SketcherPointList::~SketcherPointList()
+{
+ clear();
+}
+void SketcherPointList::clear()
+{
+ for( int i=PT_SZ; --i>=0; )
+ cols[i].remove_all_objects();
+}
+
+int SketcherPointList::column_resize_event()
+{
+ for( int i=PT_SZ; --i>=0; )
+ widths[i] = get_column_width(i);
+ return 1;
+}
+
+int SketcherPointList::handle_event()
+{
+ int pi = get_selection_number(0, 0);
+ set_selected(pi);
+ gui->send_configure_change();
+ return 1;
+}
+
+int SketcherPointList::selection_changed()
+{
+ handle_event();
+ return 1;
+}
+
+void SketcherPointList::add_point(const char *id, const char *xp, const char *yp)
+{
+ cols[PT_ID].append(new BC_ListBoxItem(id));
+ cols[PT_X].append(new BC_ListBoxItem(xp));
+ cols[PT_Y].append(new BC_ListBoxItem(yp));
+}
+
+void SketcherPointList::set_point(int i, int c, int v)
+{
+ char stxt[BCSTRLEN];
+ sprintf(stxt,"%d",v);
+ set_point(i,c,stxt);
+}
+void SketcherPointList::set_point(int i, int c, const char *cp)
+{
+ cols[c].get(i)->set_text(cp);
+}
+
+void SketcherPointList::set_selected(int k)
+{
+ SketcherPoint *pt = 0;
+ int ci = plugin->config.cv_selected, pi = -1;
+ if( ci >= 0 && ci < plugin->config.curves.size() ) {
+ SketcherCurve *cv = plugin->config.curves[ci];
+ pt = k >= 0 && k < cv->points.size() ? cv->points[pi=k] : 0;
+ }
+ gui->point_x->update(pt ? pt->x : 0.f);
+ gui->point_y->update(pt ? pt->y : 0.f);
+ plugin->config.pt_selected = pi;
+ update_selection(&cols[0], pi);
+ update_list(k);
+}
+void SketcherPointList::update_list(int k)
+{
+ int xpos = get_xposition(), ypos = get_yposition();
+ if( k < 0 ) k = get_selection_number(0, 0);
+ update_selection(&cols[0], k);
+ BC_ListBox::update(&cols[0], &titles[0],&widths[0],PT_SZ, xpos,ypos,k);
+ center_selection();
+}
+void SketcherPointList::update(int k)
+{
+ clear();
+ int ci = plugin->config.cv_selected, sz = 0;
+ if( ci >= 0 && ci < plugin->config.curves.size() ) {
+ SketcherCurve *cv = plugin->config.curves[ci];
+ SketcherPoints &points = cv->points;
+ sz = points.size();
+ for( int i=0; i<sz; ++i ) {
+ SketcherPoint *pt = points[i];
+ char itxt[BCSTRLEN]; sprintf(itxt,"%d", pt->id);
+ char xtxt[BCSTRLEN]; sprintf(xtxt,"%d", pt->x);
+ char ytxt[BCSTRLEN]; sprintf(ytxt,"%d", pt->y);
+ add_point(itxt, xtxt, ytxt);
+ }
+ }
+ set_selected(k);
+}
+
+void SketcherWindow::update_gui()
+{
+ SketcherConfig &config = plugin->config;
+ int ci = config.cv_selected;
+ int pi = config.pt_selected;
+ curve_list->update(ci);
+ point_list->update(pi);
+ SketcherCurve *cv = ci >= 0 ? config.curves[ci] : 0;
+ curve_radius->update(cv ? cv->radius : 1);
+ curve_type->update(cv ? cv->ty : TYP_OFF);
+ curve_pen->update(cv ? cv->pen : PEN_SQUARE);
+ curve_color->set_color(cv ? cv->color : BLACK);
+ SketcherPoint *pt = pi >= 0 ? cv->points[pi] : 0;
+ point_x->update(pt ? pt->x : 0);
+ point_y->update(pt ? pt->y : 0);
+ drag->update(plugin->config.drag);
+}
+
+
+SketcherPointUp::SketcherPointUp(SketcherWindow *gui, int x, int y)
+ : BC_GenericButton(x, y, _("Up"))
+{
+ this->gui = gui;
+}
+SketcherPointUp::~SketcherPointUp()
+{
+}
+
+int SketcherPointUp::handle_event()
+{
+ SketcherConfig &config = gui->plugin->config;
+ int ci = config.cv_selected;
+ if( ci < 0 || ci >= config.curves.size() )
+ return 1;
+ SketcherCurve *cv = config.curves[ci];
+ SketcherPoints &points = cv->points;
+ int sz = points.size();
+ int hot_point = gui->point_list->get_selection_number(0, 0);
+
+ if( sz > 1 && hot_point > 0 ) {
+ SketcherPoint *&pt0 = points[hot_point];
+ SketcherPoint *&pt1 = points[--hot_point];
+ SketcherPoint *t = pt0; pt0 = pt1; pt1 = t;
+ gui->point_list->update(hot_point);
+ }
+ gui->send_configure_change();
+ return 1;
+}
+
+SketcherPointDn::SketcherPointDn(SketcherWindow *gui, int x, int y)
+ : BC_GenericButton(x, y, _("Dn"))
+{
+ this->gui = gui;
+}
+SketcherPointDn::~SketcherPointDn()
+{
+}
+
+int SketcherPointDn::handle_event()
+{
+ SketcherConfig &config = gui->plugin->config;
+ int ci = config.cv_selected;
+ if( ci < 0 || ci >= config.curves.size() )
+ return 1;
+ SketcherCurve *cv = config.curves[ci];
+ SketcherPoints &points = cv->points;
+ int sz = points.size();
+ int hot_point = gui->point_list->get_selection_number(0, 0);
+ if( sz > 1 && hot_point < sz-1 ) {
+ SketcherPoint *&pt0 = points[hot_point];
+ SketcherPoint *&pt1 = points[++hot_point];
+ SketcherPoint *t = pt0; pt0 = pt1; pt1 = t;
+ gui->point_list->update(hot_point);
+ }
+ gui->send_configure_change();
+ return 1;
+}
+
+SketcherDrag::SketcherDrag(SketcherWindow *gui, int x, int y)
+ : BC_CheckBox(x, y, gui->plugin->config.drag, _("Drag"))
+{
+ this->gui = gui;
+}
+int SketcherDrag::handle_event()
+{
+ CWindowGUI *cwindow_gui = gui->plugin->server->mwindow->cwindow->gui;
+ int value = get_value();
+ if( value ) {
+ if( !gui->grab(cwindow_gui) ) {
+ update(value = 0);
+ flicker(10,50);
+ }
+ }
+ else
+ gui->ungrab(cwindow_gui);
+ gui->plugin->config.drag = value;
+ gui->send_configure_change();
+ return 1;
+}
+
+SketcherNewPoint::SketcherNewPoint(SketcherWindow *gui, Sketcher *plugin, int x, int y)
+ : BC_GenericButton(x, y, 64, _("New"))
+{
+ this->gui = gui;
+ this->plugin = plugin;
+}
+SketcherNewPoint::~SketcherNewPoint()
+{
+}
+int SketcherNewPoint::handle_event()
+{
+ int k = plugin->new_point();
+ gui->point_list->update(k);
+ gui->send_configure_change();
+ return 1;
+}
+
+SketcherDelPoint::SketcherDelPoint(SketcherWindow *gui, Sketcher *plugin, int x, int y)
+ : BC_GenericButton(x, y, 64, C_("Del"))
+{
+ this->gui = gui;
+ this->plugin = plugin;
+}
+SketcherDelPoint::~SketcherDelPoint()
+{
+}
+int SketcherDelPoint::handle_event()
+{
+ SketcherConfig &config = gui->plugin->config;
+ int ci = config.cv_selected;
+ if( ci >= 0 && ci < config.curves.size() ) {
+ SketcherCurve *cv = config.curves[ci];
+ SketcherPoints &points = cv->points;
+ int hot_point = gui->point_list->get_selection_number(0, 0);
+ if( hot_point >= 0 && hot_point < points.size() ) {
+ points.remove_object_number(hot_point);
+ gui->point_list->update(--hot_point);
+ gui->send_configure_change();
+ }
+ }
+ return 1;
+}
+
+SketcherResetCurves::SketcherResetCurves(SketcherWindow *gui, Sketcher *plugin, int x, int y)
+ : BC_GenericButton(x, y, _("Reset"))
+{
+ this->gui = gui;
+ this->plugin = plugin;
+}
+SketcherResetCurves::~SketcherResetCurves()
+{
+}
+int SketcherResetCurves::handle_event()
+{
+ SketcherConfig &config = plugin->config;
+ config.curves.remove_all_objects();
+ int ci = plugin->new_curve(0, 1, 0, BLACK);
+ gui->curve_list->update(ci);
+ gui->point_list->update(-1);
+ gui->send_configure_change();
+ return 1;
+}
+
+SketcherResetPoints::SketcherResetPoints(SketcherWindow *gui, Sketcher *plugin, int x, int y)
+ : BC_GenericButton(x, y, _("Reset"))
+{
+ this->gui = gui;
+ this->plugin = plugin;
+}
+SketcherResetPoints::~SketcherResetPoints()
+{
+}
+int SketcherResetPoints::handle_event()
+{
+ SketcherConfig &config = gui->plugin->config;
+ int ci = config.cv_selected;
+ if( ci >= 0 && ci < config.curves.size() ) {
+ SketcherCurve *cv = config.curves[ci];
+ cv->points.remove_all_objects();
+ gui->point_list->update(-1);
+ gui->send_configure_change();
+ }
+ return 1;
+}
+
--- /dev/null
+/*
+ * CINELERRA
+ * Copyright (C) 2008-2015 Adam Williams <broadcast at earthling dot net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef __CRIKEYWINDOW_H__
+#define __CRIKEYWINDOW_H__
+
+#include "guicast.h"
+#include "colorpicker.h"
+
+class Sketcher;
+class SketcherNum;
+class SketcherCurveTypeItem;
+class SketcherCurveType;
+class SketcherCurvePenItem;
+class SketcherCurvePen;
+class SketcherCurveColor;
+class SketcherCurveColorPicker;
+class SketcherCurveColorThread;
+class SketcherNewCurve;
+class SketcherDelCurve;
+class SketcherCurveUp;
+class SketcherCurveDn;
+class SketcherCurveRadius;
+class SketcherCurveList;
+class SketcherPointX;
+class SketcherPointY;
+class SketcherDrag;
+class SketcherPointList;
+class SketcherNewPoint;
+class SketcherDelPoint;
+class SketcherPointUp;
+class SketcherPointDn;
+class SketcherResetCurves;
+class SketcherResetPoints;
+class SketcherWindow;
+
+class SketcherNum : public BC_TumbleTextBox
+{
+public:
+ SketcherWindow *gui;
+
+ SketcherNum(SketcherWindow *gui, int x, int y, int output,
+ int mn=-32767, int mx=32767);
+ ~SketcherNum();
+ int update(int v) { return BC_TumbleTextBox::update((int64_t)v); }
+};
+
+class SketcherCurveTypeItem : public BC_MenuItem
+{
+public:
+ SketcherCurveTypeItem(int ty);
+ int handle_event();
+ int ty;
+};
+
+class SketcherCurveType : public BC_PopupMenu
+{
+public:
+ SketcherCurveType(SketcherWindow *gui, int x, int y, int ty);
+ void create_objects();
+ void update(int ty);
+
+ SketcherWindow *gui;
+};
+
+class SketcherCurvePenItem : public BC_MenuItem
+{
+public:
+ SketcherCurvePenItem(int pen);
+ int handle_event();
+ int pen;
+};
+
+class SketcherCurvePen : public BC_PopupMenu
+{
+public:
+ SketcherCurvePen(SketcherWindow *gui, int x, int y, int pen);
+ void create_objects();
+ void update(int ty);
+
+ SketcherWindow *gui;
+};
+
+class SketcherCurveColor : public BC_Button
+{
+public:
+ SketcherCurveColor(SketcherWindow *gui, int x, int y, int w);
+ ~SketcherCurveColor();
+
+ void set_color(int color);
+ void update_gui(int color);
+ int handle_event();
+
+ int color;
+ VFrame *vframes[3];
+ SketcherWindow *gui;
+};
+
+class SketcherCurveColorPicker : public ColorPicker
+{
+public:
+ SketcherCurveColorPicker(SketcherWindow *gui, SketcherCurveColor *curve_color);
+ ~SketcherCurveColorPicker();
+ void start(int color);
+ int handle_new_color(int color, int alpha);
+ void update_gui();
+ void handle_done_event(int result);
+
+ SketcherWindow *gui;
+ int color;
+ SketcherCurveColor *color_button;
+ SketcherCurveColorThread *color_update;
+};
+
+class SketcherCurveColorThread : public Thread
+{
+public:
+ SketcherCurveColorThread(SketcherCurveColorPicker *color_picker);
+ ~SketcherCurveColorThread();
+
+ void start();
+ void stop();
+ void run();
+
+ SketcherCurveColorPicker *color_picker;
+ int done;
+ Condition *update_lock;
+};
+
+class SketcherNewCurve : public BC_GenericButton
+{
+public:
+ SketcherNewCurve(SketcherWindow *gui, Sketcher *plugin, int x, int y);
+ ~SketcherNewCurve();
+
+ int handle_event();
+
+ SketcherWindow *gui;
+ Sketcher *plugin;
+};
+
+class SketcherDelCurve : public BC_GenericButton
+{
+public:
+ SketcherDelCurve(SketcherWindow *gui, Sketcher *plugin, int x, int y);
+ ~SketcherDelCurve();
+
+ int handle_event();
+
+ Sketcher *plugin;
+ SketcherWindow *gui;
+};
+
+class SketcherCurveUp : public BC_GenericButton
+{
+public:
+ SketcherCurveUp(SketcherWindow *gui, int x, int y);
+ ~SketcherCurveUp();
+
+ int handle_event();
+
+ SketcherWindow *gui;
+};
+
+class SketcherCurveDn : public BC_GenericButton
+{
+public:
+ SketcherCurveDn(SketcherWindow *gui, int x, int y);
+ ~SketcherCurveDn();
+
+ int handle_event();
+
+ SketcherWindow *gui;
+};
+
+class SketcherCurveRadius : public SketcherNum
+{
+public:
+ SketcherCurveRadius(SketcherWindow *gui, int x, int y, float output)
+ : SketcherNum(gui, x, y, output, 0, 255) {}
+ ~SketcherCurveRadius() {}
+
+ int handle_event();
+};
+
+class SketcherCurveList : public BC_ListBox
+{
+public:
+ SketcherCurveList(SketcherWindow *gui, Sketcher *plugin, int x, int y);
+ ~SketcherCurveList();
+
+ int handle_event();
+ int selection_changed();
+ int column_resize_event();
+ ArrayList<BC_ListBoxItem*> cols[CV_SZ];
+ void clear();
+ void add_curve(const char *id, const char *type,
+ const char *radius, const char *pen, const char *color);
+ void del_curve(int i);
+ void set_selected(int k);
+ void update(int k);
+ void update_list(int k);
+
+ SketcherWindow *gui;
+ Sketcher *plugin;
+ const char *titles[CV_SZ];
+ int widths[CV_SZ];
+};
+
+
+class SketcherPointX : public SketcherNum
+{
+public:
+ SketcherPointX(SketcherWindow *gui, int x, int y, float output)
+ : SketcherNum(gui, x, y, output) {}
+ ~SketcherPointX() {}
+
+ int handle_event();
+};
+class SketcherPointY : public SketcherNum
+{
+public:
+ SketcherPointY(SketcherWindow *gui, int x, int y, float output)
+ : SketcherNum(gui, x, y, output) {}
+ ~SketcherPointY() {}
+
+ int handle_event();
+};
+
+
+class SketcherDrag : public BC_CheckBox
+{
+public:
+ SketcherDrag(SketcherWindow *gui, int x, int y);
+
+ int handle_event();
+ SketcherWindow *gui;
+};
+
+
+class SketcherPointList : public BC_ListBox
+{
+public:
+ SketcherPointList(SketcherWindow *gui, Sketcher *plugin, int x, int y);
+ ~SketcherPointList();
+
+ int handle_event();
+ int selection_changed();
+ int column_resize_event();
+ ArrayList<BC_ListBoxItem*> cols[PT_SZ];
+ void clear();
+ void add_point(const char *id, const char *xp, const char *yp);
+ void set_point(int i, int c, int v);
+ void set_point(int i, int c, const char *cp);
+ void set_selected(int k);
+ void update(int k);
+ void update_list(int k);
+
+
+ SketcherWindow *gui;
+ Sketcher *plugin;
+ const char *titles[PT_SZ];
+ int widths[PT_SZ];
+};
+
+class SketcherNewPoint : public BC_GenericButton
+{
+public:
+ SketcherNewPoint(SketcherWindow *gui, Sketcher *plugin, int x, int y);
+ ~SketcherNewPoint();
+
+ int handle_event();
+
+ SketcherWindow *gui;
+ Sketcher *plugin;
+};
+
+class SketcherDelPoint : public BC_GenericButton
+{
+public:
+ SketcherDelPoint(SketcherWindow *gui, Sketcher *plugin, int x, int y);
+ ~SketcherDelPoint();
+
+ int handle_event();
+
+ Sketcher *plugin;
+ SketcherWindow *gui;
+};
+
+class SketcherPointUp : public BC_GenericButton
+{
+public:
+ SketcherPointUp(SketcherWindow *gui, int x, int y);
+ ~SketcherPointUp();
+
+ int handle_event();
+
+ SketcherWindow *gui;
+};
+
+class SketcherPointDn : public BC_GenericButton
+{
+public:
+ SketcherPointDn(SketcherWindow *gui, int x, int y);
+ ~SketcherPointDn();
+
+ int handle_event();
+
+ SketcherWindow *gui;
+};
+
+class SketcherResetCurves : public BC_GenericButton
+{
+public:
+ SketcherResetCurves(SketcherWindow *gui, Sketcher *plugin, int x, int y);
+ ~SketcherResetCurves();
+
+ int handle_event();
+
+ Sketcher *plugin;
+ SketcherWindow *gui;
+};
+
+class SketcherResetPoints : public BC_GenericButton
+{
+public:
+ SketcherResetPoints(SketcherWindow *gui, Sketcher *plugin, int x, int y);
+ ~SketcherResetPoints();
+
+ int handle_event();
+
+ Sketcher *plugin;
+ SketcherWindow *gui;
+};
+
+
+class SketcherWindow : public PluginClientWindow
+{
+public:
+ SketcherWindow(Sketcher *plugin);
+ ~SketcherWindow();
+
+ void create_objects();
+ void update_gui();
+ void start_color_thread(SketcherCurveColor *curve_color);
+ int grab_event(XEvent *event);
+ int do_grab_event(XEvent *event);
+ void done_event(int result);
+ void send_configure_change();
+ int keypress_event();
+
+ Sketcher *plugin;
+
+ BC_Title *title_type, *title_pen;
+ BC_Title *title_color, *title_radius;
+ SketcherCurveType *curve_type;
+ SketcherCurvePen *curve_pen;
+ SketcherCurveColor *curve_color;
+ SketcherCurveColorPicker *color_picker;
+ SketcherNewCurve *new_curve;
+ SketcherDelCurve *del_curve;
+ SketcherCurveUp *curve_up;
+ SketcherCurveDn *curve_dn;
+ SketcherCurveRadius *curve_radius;
+ SketcherCurveList *curve_list;
+ SketcherResetCurves *reset_curves;
+
+ BC_Title *title_x, *title_y;
+ SketcherPointX *point_x;
+ SketcherPointY *point_y;
+ SketcherNewPoint *new_point;
+ SketcherDelPoint *del_point;
+ SketcherPointUp *point_up;
+ SketcherPointDn *point_dn;
+ int dragging, pending_config;
+ int new_points;
+ float last_x, last_y;
+ SketcherDrag *drag;
+ SketcherPointList *point_list;
+ SketcherResetPoints *reset_points;
+ BC_Title *notes0, *notes1, *notes2;
+};
+#endif
+
int TitleColorButton::handle_event()
{
window->color_thread->start_window(client->config.color,
- client->config.alpha);
+ client->config.alpha, 1);
return 1;
}
TitleOutlineColorButton::TitleOutlineColorButton(TitleMain *client, TitleWindow *window, int x, int y)
int TitleOutlineColorButton::handle_event()
{
window->outline_color_thread->start_window(client->config.outline_color,
- client->config.outline_alpha);
+ client->config.outline_alpha, 1);
return 1;
}
return 1;
}
+void TitleColorThread::handle_done_event(int result)
+{
+ if( result ) {
+ client->config.color = orig_color;
+ client->config.alpha = orig_alpha;
+ handle_new_color(orig_color, orig_alpha);
+ window->update_color();
+ window->send_configure_change();
+ }
+}
+
TitleDrag::TitleDrag(TitleMain *client, TitleWindow *window, int x, int y)
: DragCheckBox(client->server->mwindow, x, y, _("Drag"), &client->config.drag,
client->config.title_x, client->config.title_y,
}
void TitleColorPopup::handle_done_event(int result)
{
- if( result ) return;
- char txt[BCSTRLEN]; sprintf(txt, "<%s #%06x>", _(KW_COLOR), color_value);
- window->insert_ibeam(txt);
+ if( !result ) {
+ char txt[BCSTRLEN]; sprintf(txt, "<%s #%06x>", _(KW_COLOR), color_value);
+ window->insert_ibeam(txt);
+ }
}
TitlePngPopup::TitlePngPopup(TitleMain *client, TitleWindow *window)
public:
TitleColorThread(TitleMain *client, TitleWindow *window, int is_outline);
virtual int handle_new_color(int output, int alpha);
+ void handle_done_event(int result);
TitleMain *client;
TitleWindow *window;
int is_outline;
+++ /dev/null
-diff -urN a/fftools/cmdutils.c b/fftools/cmdutils.c
---- a/fftools/cmdutils.c 2018-10-01 10:52:48.866784675 -0600
-+++ b/fftools/cmdutils.c 2018-10-01 10:52:55.550799827 -0600
-@@ -1179,6 +1179,7 @@
-
- void show_banner(int argc, char **argv, const OptionDef *options)
- {
-+ return;
- int idx = locate_option(argc, argv, options, "version");
- if (hide_banner || idx)
- return;
+++ /dev/null
-diff -urN a/libavformat/bluray.c b/libavformat/bluray.c
---- a/libavformat/bluray.c 2018-04-13 17:34:28.000000000 -0600
-+++ b/libavformat/bluray.c 2018-04-24 11:02:19.724232178 -0600
-@@ -28,7 +28,7 @@
- #include "libavutil/opt.h"
-
- #define BLURAY_PROTO_PREFIX "bluray:"
--#define MIN_PLAYLIST_LENGTH 180 /* 3 min */
-+#define MIN_PLAYLIST_LENGTH 0
-
- typedef struct {
- const AVClass *class;
+++ /dev/null
-diff -urN a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
---- a/libavformat/mpegtsenc.c 2018-04-20 04:02:57.000000000 -0600
-+++ b/libavformat/mpegtsenc.c 2018-04-24 10:27:57.193689213 -0600
-@@ -56,9 +56,8 @@
- int sid; /* service ID */
- char *name;
- char *provider_name;
-- int pcr_pid;
-- int pcr_packet_count;
-- int pcr_packet_period;
-+ int64_t pcr, pcr_packet_timer, pcr_packet_period;
-+ int pcr_sid, pcr_pid;
- AVProgram *program;
- } MpegTSService;
-
-@@ -78,14 +77,12 @@
- MpegTSSection pat; /* MPEG-2 PAT table */
- MpegTSSection sdt; /* MPEG-2 SDT table context */
- MpegTSService **services;
-- int sdt_packet_count;
-- int sdt_packet_period;
-- int pat_packet_count;
-- int pat_packet_period;
-+ int64_t sdt_packet_timer, sdt_packet_period;
-+ int64_t pat_packet_timer, pat_packet_period;
- int nb_services;
- int onid;
- int tsid;
-- int64_t first_pcr;
-+ int64_t pcr, first_pcr, delay;
- int mux_rate; ///< set to 1 when VBR
- int pes_payload_size;
-
-@@ -95,12 +92,14 @@
- int service_type;
-
- int pmt_start_pid;
-+ int pcr_start_pid;
- int start_pid;
- int m2ts_mode;
-+ int64_t ts_offset;
-
- int reemit_pat_pmt; // backward compatibility
-
-- int pcr_period;
-+ double pcr_period;
- #define MPEGTS_FLAG_REEMIT_PAT_PMT 0x01
- #define MPEGTS_FLAG_AAC_LATM 0x02
- #define MPEGTS_FLAG_PAT_PMT_AT_FRAMES 0x04
-@@ -111,8 +110,6 @@
- int tables_version;
- double pat_period;
- double sdt_period;
-- int64_t last_pat_ts;
-- int64_t last_sdt_ts;
-
- int omit_video_pes_length;
- } MpegTSWrite;
-@@ -222,10 +219,10 @@
- #define DEFAULT_PROVIDER_NAME "FFmpeg"
- #define DEFAULT_SERVICE_NAME "Service01"
-
--/* we retransmit the SI info at this rate */
-+/* we retransmit the SI info at this rate (ms) */
- #define SDT_RETRANS_TIME 500
- #define PAT_RETRANS_TIME 100
--#define PCR_RETRANS_TIME 20
-+#define PCR_RETRANS_TIME 50
-
- typedef struct MpegTSWriteStream {
- struct MpegTSService *service;
-@@ -721,6 +718,7 @@
- service->pmt.pid = ts->pmt_start_pid + ts->nb_services;
- service->sid = sid;
- service->pcr_pid = 0x1fff;
-+ service->pcr_sid = 0x1fff;
- service->provider_name = av_strdup(provider_name);
- service->name = av_strdup(name);
- if (!service->provider_name || !service->name)
-@@ -736,18 +734,11 @@
- return NULL;
- }
-
--static int64_t get_pcr(const MpegTSWrite *ts, AVIOContext *pb)
--{
-- return av_rescale(avio_tell(pb) + 11, 8 * PCR_TIME_BASE, ts->mux_rate) +
-- ts->first_pcr;
--}
--
- static void mpegts_prefix_m2ts_header(AVFormatContext *s)
- {
- MpegTSWrite *ts = s->priv_data;
- if (ts->m2ts_mode) {
-- int64_t pcr = get_pcr(s->priv_data, s->pb);
-- uint32_t tp_extra_header = pcr % 0x3fffffff;
-+ uint32_t tp_extra_header = ts->pcr % 0x3fffffff;
- tp_extra_header = AV_RB32(&tp_extra_header);
- avio_write(s->pb, (unsigned char *) &tp_extra_header,
- sizeof(tp_extra_header));
-@@ -768,6 +759,7 @@
- MpegTSService *service;
- AVStream *st, *pcr_st = NULL;
- AVDictionaryEntry *title, *provider;
-+ double clk_rate;
- int i, j;
- const char *service_name;
- const char *provider_name;
-@@ -776,6 +768,15 @@
-
- if (s->max_delay < 0) /* Not set by the caller */
- s->max_delay = 0;
-+ ts->delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE);
-+
-+ if (ts->m2ts_mode == -1) {
-+ if (av_match_ext(s->url, "m2ts")) {
-+ ts->m2ts_mode = 1;
-+ } else {
-+ ts->m2ts_mode = 0;
-+ }
-+ }
-
- // round up to a whole number of TS packets
- ts->pes_payload_size = (ts->pes_payload_size + 14 + 183) / 184 * 184 - 14;
-@@ -822,6 +823,8 @@
- service->program = program;
- }
- }
-+ if (ts->m2ts_mode > 1)
-+ service->pmt.pid = 0x00ff + ts->service_id;
-
- ts->pat.pid = PAT_PID;
- /* Initialize at 15 so that it wraps and is equal to 0 for the
-@@ -907,10 +910,9 @@
- ts_st->discontinuity = ts->flags & MPEGTS_FLAG_DISCONT;
- /* update PCR pid by using the first video stream */
- if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
-- service->pcr_pid == 0x1fff) {
-- service->pcr_pid = ts_st->pid;
-+ service->pcr_sid == 0x1fff)
- pcr_st = st;
-- }
-+
- if (st->codecpar->codec_id == AV_CODEC_ID_AAC &&
- st->codecpar->extradata_size > 0) {
- AVStream *ast;
-@@ -946,78 +948,47 @@
- av_freep(&pids);
-
- /* if no video stream, use the first stream as PCR */
-- if (service->pcr_pid == 0x1fff && s->nb_streams > 0) {
-- pcr_st = s->streams[0];
-- ts_st = pcr_st->priv_data;
-- service->pcr_pid = ts_st->pid;
-- } else
-- ts_st = pcr_st->priv_data;
--
-- if (ts->mux_rate > 1) {
-- service->pcr_packet_period = (int64_t)ts->mux_rate * ts->pcr_period /
-- (TS_PACKET_SIZE * 8 * 1000);
-- ts->sdt_packet_period = (int64_t)ts->mux_rate * SDT_RETRANS_TIME /
-- (TS_PACKET_SIZE * 8 * 1000);
-- ts->pat_packet_period = (int64_t)ts->mux_rate * PAT_RETRANS_TIME /
-- (TS_PACKET_SIZE * 8 * 1000);
--
-- if (ts->copyts < 1)
-- ts->first_pcr = av_rescale(s->max_delay, PCR_TIME_BASE, AV_TIME_BASE);
-- } else {
-- /* Arbitrary values, PAT/PMT will also be written on video key frames */
-- ts->sdt_packet_period = 200;
-- ts->pat_packet_period = 40;
-- if (pcr_st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
-- int frame_size = av_get_audio_frame_duration2(pcr_st->codecpar, 0);
-- if (!frame_size) {
-- av_log(s, AV_LOG_WARNING, "frame size not set\n");
-- service->pcr_packet_period =
-- pcr_st->codecpar->sample_rate / (10 * 512);
-- } else {
-- service->pcr_packet_period =
-- pcr_st->codecpar->sample_rate / (10 * frame_size);
-- }
-- } else {
-- // max delta PCR 0.1s
-- // TODO: should be avg_frame_rate
-- service->pcr_packet_period =
-- ts_st->user_tb.den / (10 * ts_st->user_tb.num);
-- }
-- if (!service->pcr_packet_period)
-- service->pcr_packet_period = 1;
-- }
--
-- ts->last_pat_ts = AV_NOPTS_VALUE;
-- ts->last_sdt_ts = AV_NOPTS_VALUE;
-- // The user specified a period, use only it
-- if (ts->pat_period < INT_MAX/2) {
-- ts->pat_packet_period = INT_MAX;
-+ if (!pcr_st && s->nb_streams > 0)
-+ pcr_st = s->streams[0];
-+ if (!pcr_st) {
-+ av_log(s, AV_LOG_ERROR, "no streams\n");
-+ ret = AVERROR(EINVAL);
-+ goto fail;
- }
-- if (ts->sdt_period < INT_MAX/2) {
-- ts->sdt_packet_period = INT_MAX;
-+ ts_st = pcr_st->priv_data;
-+ if (service->pcr_sid == 0x1fff)
-+ service->pcr_sid = ts_st->pid;
-+ if (service->pcr_pid == 0x1fff)
-+ service->pcr_pid = ts->m2ts_mode > 1 ?
-+ 0x1000 + ts->service_id : service->pcr_sid ;
-+ if (service->pmt.pid == service->pcr_pid) {
-+ av_log(s, AV_LOG_ERROR, "Duplicate stream id %d\n", service->pcr_pid);
-+ ret = AVERROR(EINVAL);
-+ goto fail;
- }
-
-+ clk_rate = ts->mux_rate > 1 ? ts->mux_rate : PCR_TIME_BASE;
-+ ts->sdt_packet_period = ts->sdt_period < 0 ? -1 : ts->sdt_period/1000 * clk_rate;
-+ ts->pat_packet_period = ts->pat_period/1000 * clk_rate;
-+ service->pcr_packet_period = ts->pcr_period/1000 * clk_rate;
-+ if (service->pcr_packet_period < (TS_PACKET_SIZE*8*10))
-+ service->pcr_packet_period = (TS_PACKET_SIZE*8*10);
-+ av_log(s, AV_LOG_VERBOSE, "clk_rate %f: ticks/pkt %d pcr, %d sdt, %d pmt\n", clk_rate,
-+ (int)service->pcr_packet_period, (int)ts->sdt_packet_period, (int)ts->pat_packet_period);
-+
-+ if (ts->copyts < 1)
-+ ts->first_pcr = av_rescale(s->max_delay, PCR_TIME_BASE, AV_TIME_BASE);
-+
- // output a PCR as soon as possible
-- service->pcr_packet_count = service->pcr_packet_period;
-- ts->pat_packet_count = ts->pat_packet_period - 1;
-- ts->sdt_packet_count = ts->sdt_packet_period - 1;
-+ ts->pcr = 0;
-+ service->pcr_packet_timer = 0;
-+ ts->pat_packet_timer = 0;
-+ ts->sdt_packet_timer = 0;
-
- if (ts->mux_rate == 1)
- av_log(s, AV_LOG_VERBOSE, "muxrate VBR, ");
- else
- av_log(s, AV_LOG_VERBOSE, "muxrate %d, ", ts->mux_rate);
-- av_log(s, AV_LOG_VERBOSE,
-- "pcr every %d pkts, sdt every %d, pat/pmt every %d pkts\n",
-- service->pcr_packet_period,
-- ts->sdt_packet_period, ts->pat_packet_period);
--
-- if (ts->m2ts_mode == -1) {
-- if (av_match_ext(s->url, "m2ts")) {
-- ts->m2ts_mode = 1;
-- } else {
-- ts->m2ts_mode = 0;
-- }
-- }
-
- return 0;
-
-@@ -1032,22 +1003,12 @@
- MpegTSWrite *ts = s->priv_data;
- int i;
-
-- if (++ts->sdt_packet_count == ts->sdt_packet_period ||
-- (dts != AV_NOPTS_VALUE && ts->last_sdt_ts == AV_NOPTS_VALUE) ||
-- (dts != AV_NOPTS_VALUE && dts - ts->last_sdt_ts >= ts->sdt_period*90000.0)
-- ) {
-- ts->sdt_packet_count = 0;
-- if (dts != AV_NOPTS_VALUE)
-- ts->last_sdt_ts = FFMAX(dts, ts->last_sdt_ts);
-+ if ( ts->sdt_packet_period >= 0 && ts->pcr >= ts->sdt_packet_timer ) {
-+ ts->sdt_packet_timer = ts->pcr + ts->sdt_packet_period;
- mpegts_write_sdt(s);
- }
-- if (++ts->pat_packet_count == ts->pat_packet_period ||
-- (dts != AV_NOPTS_VALUE && ts->last_pat_ts == AV_NOPTS_VALUE) ||
-- (dts != AV_NOPTS_VALUE && dts - ts->last_pat_ts >= ts->pat_period*90000.0) ||
-- force_pat) {
-- ts->pat_packet_count = 0;
-- if (dts != AV_NOPTS_VALUE)
-- ts->last_pat_ts = FFMAX(dts, ts->last_pat_ts);
-+ if (ts->pcr >= ts->pat_packet_timer || force_pat) {
-+ ts->pat_packet_timer = ts->pcr + ts->pat_packet_period;
- mpegts_write_pat(s);
- for (i = 0; i < ts->nb_services; i++)
- mpegts_write_pmt(s, ts->services[i]);
-@@ -1089,13 +1050,14 @@
- {
- MpegTSWrite *ts = s->priv_data;
- MpegTSWriteStream *ts_st = st->priv_data;
-+ uint32_t pcr_pid = ts_st->service->pcr_pid;
- uint8_t *q;
- uint8_t buf[TS_PACKET_SIZE];
-
- q = buf;
- *q++ = 0x47;
-- *q++ = ts_st->pid >> 8;
-- *q++ = ts_st->pid;
-+ *q++ = pcr_pid >> 8;
-+ *q++ = pcr_pid;
- *q++ = 0x20 | ts_st->cc; /* Adaptation only */
- /* Continuity Count field does not increment (see 13818-1 section 2.4.3.3) */
- *q++ = TS_PACKET_SIZE - 5; /* Adaptation Field Length */
-@@ -1106,7 +1068,7 @@
- }
-
- /* PCR coded into 6 bytes */
-- q += write_pcr_bits(q, get_pcr(ts, s->pb));
-+ q += write_pcr_bits(q, ts->pcr);
-
- /* stuffing bytes */
- memset(q, 0xFF, TS_PACKET_SIZE - (q - buf));
-@@ -1175,8 +1137,6 @@
- uint8_t *q;
- int val, is_start, len, header_len, write_pcr, is_dvb_subtitle, is_dvb_teletext, flags;
- int afc_len, stuffing_len;
-- int64_t pcr = -1; /* avoid warning */
-- int64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE);
- int force_pat = st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && key && !ts_st->prev_payload_key;
-
- av_assert0(ts_st->payload != buf || st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO);
-@@ -1186,28 +1146,33 @@
-
- is_start = 1;
- while (payload_size > 0) {
-+ ts->pcr = ts->first_pcr + (ts->mux_rate == 1 ?
-+ (dts == AV_NOPTS_VALUE ? 0 : (dts - ts->delay) * 300) :
-+ // add 11, pcr references the last byte of program clock reference base
-+ av_rescale(avio_tell(s->pb) + 11, 8 * PCR_TIME_BASE, ts->mux_rate));
-+
- retransmit_si_info(s, force_pat, dts);
- force_pat = 0;
-
- write_pcr = 0;
-- if (ts_st->pid == ts_st->service->pcr_pid) {
-- if (ts->mux_rate > 1 || is_start) // VBR pcr period is based on frames
-- ts_st->service->pcr_packet_count++;
-- if (ts_st->service->pcr_packet_count >=
-- ts_st->service->pcr_packet_period) {
-- ts_st->service->pcr_packet_count = 0;
-+ if (ts_st->pid == ts_st->service->pcr_sid) {
-+ if( ts->pcr >= ts_st->service->pcr_packet_timer ) {
-+ ts_st->service->pcr_packet_timer = ts->pcr + ts_st->service->pcr_packet_period;
- write_pcr = 1;
- }
- }
--
-+ if (write_pcr && ts_st->service->pcr_sid != ts_st->service->pcr_pid) {
-+ mpegts_insert_pcr_only(s, st);
-+ continue;
-+ }
- if (ts->mux_rate > 1 && dts != AV_NOPTS_VALUE &&
-- (dts - get_pcr(ts, s->pb) / 300) > delay) {
-- /* pcr insert gets priority over null packet insert */
-- if (write_pcr)
-- mpegts_insert_pcr_only(s, st);
-+ (dts - ts->pcr / 300) > ts->delay) {
-+ /* pcr insert gets priority over null packet insert */
-+ if (write_pcr)
-+ mpegts_insert_pcr_only(s, st);
- else
-- mpegts_insert_null_packet(s);
-- /* recalculate write_pcr and possibly retransmit si_info */
-+ mpegts_insert_null_packet(s);
-+ /* recalculate write_pcr and possibly retransimit si_info */
- continue;
- }
-
-@@ -1217,6 +1182,10 @@
- val = ts_st->pid >> 8;
- if (is_start)
- val |= 0x40;
-+ if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
-+ st->codecpar->codec_id == AV_CODEC_ID_AC3 &&
-+ ts->m2ts_mode > 1)
-+ val |= 0x20;
- *q++ = val;
- *q++ = ts_st->pid;
- ts_st->cc = ts_st->cc + 1 & 0xf;
-@@ -1228,7 +1197,7 @@
- }
- if (key && is_start && pts != AV_NOPTS_VALUE) {
- // set Random Access for key frames
-- if (ts_st->pid == ts_st->service->pcr_pid)
-+ if (ts_st->pid == ts_st->service->pcr_sid)
- write_pcr = 1;
- set_af_flag(buf, 0x40);
- q = get_ts_payload_start(buf);
-@@ -1236,14 +1205,10 @@
- if (write_pcr) {
- set_af_flag(buf, 0x10);
- q = get_ts_payload_start(buf);
-- // add 11, pcr references the last byte of program clock reference base
- if (ts->mux_rate > 1)
-- pcr = get_pcr(ts, s->pb);
-- else
-- pcr = (dts - delay) * 300;
-- if (dts != AV_NOPTS_VALUE && dts < pcr / 300)
-+ if (dts != AV_NOPTS_VALUE && dts < ts->pcr / 300)
- av_log(s, AV_LOG_WARNING, "dts < pcr, TS is invalid\n");
-- extend_af(buf, write_pcr_bits(q, pcr));
-+ extend_af(buf, write_pcr_bits(q, ts->pcr));
- q = get_ts_payload_start(buf);
- }
- if (is_start) {
-@@ -1344,11 +1309,13 @@
- *q++ = flags;
- *q++ = header_len;
- if (pts != AV_NOPTS_VALUE) {
-- write_pts(q, flags >> 6, pts);
-+ int64_t ts_pts = pts + ts->ts_offset;
-+ write_pts(q, flags >> 6, ts_pts);
- q += 5;
- }
- if (dts != AV_NOPTS_VALUE && pts != AV_NOPTS_VALUE && dts != pts) {
-- write_pts(q, 1, dts);
-+ int64_t ts_dts = dts + ts->ts_offset;
-+ write_pts(q, 1, ts_dts);
- q += 5;
- }
- if (pes_extension && st->codecpar->codec_id == AV_CODEC_ID_DIRAC) {
-@@ -1519,7 +1486,6 @@
- uint8_t *data = NULL;
- MpegTSWrite *ts = s->priv_data;
- MpegTSWriteStream *ts_st = st->priv_data;
-- const int64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE) * 2;
- int64_t dts = pkt->dts, pts = pkt->pts;
- int opus_samples = 0;
- int side_data_size;
-@@ -1540,16 +1506,15 @@
- }
-
- if (ts->flags & MPEGTS_FLAG_REEMIT_PAT_PMT) {
-- ts->pat_packet_count = ts->pat_packet_period - 1;
-- ts->sdt_packet_count = ts->sdt_packet_period - 1;
-+ ts->pat_packet_timer = ts->sdt_packet_timer = 0;
- ts->flags &= ~MPEGTS_FLAG_REEMIT_PAT_PMT;
- }
-
- if (ts->copyts < 1) {
- if (pts != AV_NOPTS_VALUE)
-- pts += delay;
-+ pts += 2*ts->delay;
- if (dts != AV_NOPTS_VALUE)
-- dts += delay;
-+ dts += 2*ts->delay;
- }
-
- if (ts_st->first_pts_check && pts == AV_NOPTS_VALUE) {
-@@ -1737,7 +1702,7 @@
- AVStream *st2 = s->streams[i];
- MpegTSWriteStream *ts_st2 = st2->priv_data;
- if ( ts_st2->payload_size
-- && (ts_st2->payload_dts == AV_NOPTS_VALUE || dts - ts_st2->payload_dts > delay/2)) {
-+ && (ts_st2->payload_dts == AV_NOPTS_VALUE || dts - ts_st2->payload_dts > ts->delay)) {
- mpegts_write_pes(s, st2, ts_st2->payload, ts_st2->payload_size,
- ts_st2->payload_pts, ts_st2->payload_dts,
- ts_st2->payload_flags & AV_PKT_FLAG_KEY, stream_id);
-@@ -1908,12 +1873,18 @@
- { "mpegts_pmt_start_pid", "Set the first pid of the PMT.",
- offsetof(MpegTSWrite, pmt_start_pid), AV_OPT_TYPE_INT,
- { .i64 = 0x1000 }, 0x0010, 0x1f00, AV_OPT_FLAG_ENCODING_PARAM },
-+ { "mpegts_pcr_start_pid", "Set the first pid of the PCR.",
-+ offsetof(MpegTSWrite, pcr_start_pid), AV_OPT_TYPE_INT,
-+ { .i64 = 0x1000 }, 0x0010, 0x1f00, AV_OPT_FLAG_ENCODING_PARAM },
- { "mpegts_start_pid", "Set the first pid.",
- offsetof(MpegTSWrite, start_pid), AV_OPT_TYPE_INT,
- { .i64 = 0x0100 }, 0x0010, 0x0f00, AV_OPT_FLAG_ENCODING_PARAM },
- { "mpegts_m2ts_mode", "Enable m2ts mode.",
- offsetof(MpegTSWrite, m2ts_mode), AV_OPT_TYPE_BOOL,
-- { .i64 = -1 }, -1, 1, AV_OPT_FLAG_ENCODING_PARAM },
-+ { .i64 = -1 }, -1, 2, AV_OPT_FLAG_ENCODING_PARAM },
-+ { "mpegts_pcr_offset", "clock offset.",
-+ offsetof(MpegTSWrite, ts_offset), AV_OPT_TYPE_BOOL,
-+ { .i64 = 0 }, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
- { "muxrate", NULL,
- offsetof(MpegTSWrite, mux_rate), AV_OPT_TYPE_INT,
- { .i64 = 1 }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
-@@ -1951,15 +1922,15 @@
- { "omit_video_pes_length", "Omit the PES packet length for video packets",
- offsetof(MpegTSWrite, omit_video_pes_length), AV_OPT_TYPE_BOOL,
- { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
-- { "pcr_period", "PCR retransmission time in milliseconds",
-- offsetof(MpegTSWrite, pcr_period), AV_OPT_TYPE_INT,
-- { .i64 = PCR_RETRANS_TIME }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
-- { "pat_period", "PAT/PMT retransmission time limit in seconds",
-+ { "pcr_period", "PCR retransmission time limit in msecs",
-+ offsetof(MpegTSWrite, pcr_period), AV_OPT_TYPE_DOUBLE,
-+ { .dbl = PCR_RETRANS_TIME }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
-+ { "pat_period", "PAT/PMT retransmission time limit in msecs",
- offsetof(MpegTSWrite, pat_period), AV_OPT_TYPE_DOUBLE,
-- { .dbl = INT_MAX }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
-- { "sdt_period", "SDT retransmission time limit in seconds",
-+ { .dbl = PAT_RETRANS_TIME }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
-+ { "sdt_period", "SDT retransmission time limit in msecs",
- offsetof(MpegTSWrite, sdt_period), AV_OPT_TYPE_DOUBLE,
-- { .dbl = INT_MAX }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
-+ { .dbl = SDT_RETRANS_TIME }, -1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
- { NULL },
- };
-
+++ /dev/null
-diff -urN a/libavformat/avformat.h b/libavformat/avformat.h
---- a/libavformat/avformat.h 2018-04-20 04:02:57.000000000 -0600
-+++ b/libavformat/avformat.h 2018-04-24 11:02:20.777232001 -0600
-@@ -487,6 +487,9 @@
- The user or muxer can override this through
- AVFormatContext.avoid_negative_ts
- */
-+#define AVFMT_SEEK_NOSTREAMS 0x80000 /**< Stream index ignored by seek,
-+ or some streams fail to seek
-+ */
-
- #define AVFMT_SEEK_TO_PTS 0x4000000 /**< Seeking is based on PTS */
-
-@@ -647,7 +650,8 @@
- /**
- * Can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER, AVFMT_SHOW_IDS,
- * AVFMT_GENERIC_INDEX, AVFMT_TS_DISCONT, AVFMT_NOBINSEARCH,
-- * AVFMT_NOGENSEARCH, AVFMT_NO_BYTE_SEEK, AVFMT_SEEK_TO_PTS.
-+ * AVFMT_NOGENSEARCH, AVFMT_NO_BYTE_SEEK, AVFMT_SEEK_TO_PTS,
-+ * AVFMT_SEEK_NOSTREAMS
- */
- int flags;
-
-diff -urN a/libavformat/dv.c b/libavformat/dv.c
---- a/libavformat/dv.c 2018-04-13 17:34:28.000000000 -0600
-+++ b/libavformat/dv.c 2018-04-24 11:02:20.778232001 -0600
-@@ -632,6 +632,7 @@
- AVInputFormat ff_dv_demuxer = {
- .name = "dv",
- .long_name = NULL_IF_CONFIG_SMALL("DV (Digital Video)"),
-+ .flags = AVFMT_SEEK_NOSTREAMS,
- .priv_data_size = sizeof(RawDVContext),
- .read_probe = dv_probe,
- .read_header = dv_read_header,
-diff -urN a/libavformat/matroskadec.c b/libavformat/matroskadec.c
---- a/libavformat/matroskadec.c 2018-04-20 04:02:57.000000000 -0600
-+++ b/libavformat/matroskadec.c 2018-04-24 11:02:20.779232001 -0600
-@@ -4026,6 +4026,7 @@
- AVInputFormat ff_matroska_demuxer = {
- .name = "matroska,webm",
- .long_name = NULL_IF_CONFIG_SMALL("Matroska / WebM"),
-+ .flags = AVFMT_SEEK_NOSTREAMS,
- .extensions = "mkv,mk3d,mka,mks",
- .priv_data_size = sizeof(MatroskaDemuxContext),
- .read_probe = matroska_probe,
-@@ -4039,6 +4040,7 @@
- AVInputFormat ff_webm_dash_manifest_demuxer = {
- .name = "webm_dash_manifest",
- .long_name = NULL_IF_CONFIG_SMALL("WebM DASH Manifest"),
-+ .flags = AVFMT_SEEK_NOSTREAMS,
- .priv_data_size = sizeof(MatroskaDemuxContext),
- .read_header = webm_dash_manifest_read_header,
- .read_packet = webm_dash_manifest_read_packet,
-diff -urN a/libavformat/utils.c b/libavformat/utils.c
---- a/libavformat/utils.c 2018-04-20 04:02:58.000000000 -0600
-+++ b/libavformat/utils.c 2018-04-24 11:02:20.780232001 -0600
-@@ -2471,6 +2471,13 @@
- return seek_frame_byte(s, stream_index, timestamp, flags);
- }
-
-+ if (stream_index != -1 && (s->iformat->flags & AVFMT_SEEK_NOSTREAMS)) {
-+ timestamp = av_rescale_q(timestamp,
-+ s->streams[stream_index]->time_base,
-+ AV_TIME_BASE_Q);
-+ stream_index = -1;
-+ }
-+
- if (stream_index < 0) {
- stream_index = av_find_default_stream_index(s);
- if (stream_index < 0)
--- /dev/null
+diff -urN a/fftools/cmdutils.c b/fftools/cmdutils.c
+--- a/fftools/cmdutils.c 2018-10-01 10:52:48.866784675 -0600
++++ b/fftools/cmdutils.c 2018-10-01 10:52:55.550799827 -0600
+@@ -1179,6 +1179,7 @@
+
+ void show_banner(int argc, char **argv, const OptionDef *options)
+ {
++ return;
+ int idx = locate_option(argc, argv, options, "version");
+ if (hide_banner || idx)
+ return;
--- /dev/null
+diff -urN a/libavformat/bluray.c b/libavformat/bluray.c
+--- a/libavformat/bluray.c 2018-04-13 17:34:28.000000000 -0600
++++ b/libavformat/bluray.c 2018-04-24 11:02:19.724232178 -0600
+@@ -28,7 +28,7 @@
+ #include "libavutil/opt.h"
+
+ #define BLURAY_PROTO_PREFIX "bluray:"
+-#define MIN_PLAYLIST_LENGTH 180 /* 3 min */
++#define MIN_PLAYLIST_LENGTH 0
+
+ typedef struct {
+ const AVClass *class;
--- /dev/null
+diff -urN a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
+--- a/libavformat/mpegtsenc.c 2018-04-20 04:02:57.000000000 -0600
++++ b/libavformat/mpegtsenc.c 2018-04-24 10:27:57.193689213 -0600
+@@ -56,9 +56,8 @@
+ int sid; /* service ID */
+ char *name;
+ char *provider_name;
+- int pcr_pid;
+- int pcr_packet_count;
+- int pcr_packet_period;
++ int64_t pcr, pcr_packet_timer, pcr_packet_period;
++ int pcr_sid, pcr_pid;
+ AVProgram *program;
+ } MpegTSService;
+
+@@ -78,14 +77,12 @@
+ MpegTSSection pat; /* MPEG-2 PAT table */
+ MpegTSSection sdt; /* MPEG-2 SDT table context */
+ MpegTSService **services;
+- int sdt_packet_count;
+- int sdt_packet_period;
+- int pat_packet_count;
+- int pat_packet_period;
++ int64_t sdt_packet_timer, sdt_packet_period;
++ int64_t pat_packet_timer, pat_packet_period;
+ int nb_services;
+ int onid;
+ int tsid;
+- int64_t first_pcr;
++ int64_t pcr, first_pcr, delay;
+ int mux_rate; ///< set to 1 when VBR
+ int pes_payload_size;
+
+@@ -95,12 +92,14 @@
+ int service_type;
+
+ int pmt_start_pid;
++ int pcr_start_pid;
+ int start_pid;
+ int m2ts_mode;
++ int64_t ts_offset;
+
+ int reemit_pat_pmt; // backward compatibility
+
+- int pcr_period;
++ double pcr_period;
+ #define MPEGTS_FLAG_REEMIT_PAT_PMT 0x01
+ #define MPEGTS_FLAG_AAC_LATM 0x02
+ #define MPEGTS_FLAG_PAT_PMT_AT_FRAMES 0x04
+@@ -111,8 +110,6 @@
+ int tables_version;
+ double pat_period;
+ double sdt_period;
+- int64_t last_pat_ts;
+- int64_t last_sdt_ts;
+
+ int omit_video_pes_length;
+ } MpegTSWrite;
+@@ -222,10 +219,10 @@
+ #define DEFAULT_PROVIDER_NAME "FFmpeg"
+ #define DEFAULT_SERVICE_NAME "Service01"
+
+-/* we retransmit the SI info at this rate */
++/* we retransmit the SI info at this rate (ms) */
+ #define SDT_RETRANS_TIME 500
+ #define PAT_RETRANS_TIME 100
+-#define PCR_RETRANS_TIME 20
++#define PCR_RETRANS_TIME 50
+
+ typedef struct MpegTSWriteStream {
+ struct MpegTSService *service;
+@@ -721,6 +718,7 @@
+ service->pmt.pid = ts->pmt_start_pid + ts->nb_services;
+ service->sid = sid;
+ service->pcr_pid = 0x1fff;
++ service->pcr_sid = 0x1fff;
+ service->provider_name = av_strdup(provider_name);
+ service->name = av_strdup(name);
+ if (!service->provider_name || !service->name)
+@@ -736,18 +734,11 @@
+ return NULL;
+ }
+
+-static int64_t get_pcr(const MpegTSWrite *ts, AVIOContext *pb)
+-{
+- return av_rescale(avio_tell(pb) + 11, 8 * PCR_TIME_BASE, ts->mux_rate) +
+- ts->first_pcr;
+-}
+-
+ static void mpegts_prefix_m2ts_header(AVFormatContext *s)
+ {
+ MpegTSWrite *ts = s->priv_data;
+ if (ts->m2ts_mode) {
+- int64_t pcr = get_pcr(s->priv_data, s->pb);
+- uint32_t tp_extra_header = pcr % 0x3fffffff;
++ uint32_t tp_extra_header = ts->pcr % 0x3fffffff;
+ tp_extra_header = AV_RB32(&tp_extra_header);
+ avio_write(s->pb, (unsigned char *) &tp_extra_header,
+ sizeof(tp_extra_header));
+@@ -768,6 +759,7 @@
+ MpegTSService *service;
+ AVStream *st, *pcr_st = NULL;
+ AVDictionaryEntry *title, *provider;
++ double clk_rate;
+ int i, j;
+ const char *service_name;
+ const char *provider_name;
+@@ -776,6 +768,15 @@
+
+ if (s->max_delay < 0) /* Not set by the caller */
+ s->max_delay = 0;
++ ts->delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE);
++
++ if (ts->m2ts_mode == -1) {
++ if (av_match_ext(s->url, "m2ts")) {
++ ts->m2ts_mode = 1;
++ } else {
++ ts->m2ts_mode = 0;
++ }
++ }
+
+ // round up to a whole number of TS packets
+ ts->pes_payload_size = (ts->pes_payload_size + 14 + 183) / 184 * 184 - 14;
+@@ -822,6 +823,8 @@
+ service->program = program;
+ }
+ }
++ if (ts->m2ts_mode > 1)
++ service->pmt.pid = 0x00ff + ts->service_id;
+
+ ts->pat.pid = PAT_PID;
+ /* Initialize at 15 so that it wraps and is equal to 0 for the
+@@ -907,10 +910,9 @@
+ ts_st->discontinuity = ts->flags & MPEGTS_FLAG_DISCONT;
+ /* update PCR pid by using the first video stream */
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
+- service->pcr_pid == 0x1fff) {
+- service->pcr_pid = ts_st->pid;
++ service->pcr_sid == 0x1fff)
+ pcr_st = st;
+- }
++
+ if (st->codecpar->codec_id == AV_CODEC_ID_AAC &&
+ st->codecpar->extradata_size > 0) {
+ AVStream *ast;
+@@ -946,78 +948,47 @@
+ av_freep(&pids);
+
+ /* if no video stream, use the first stream as PCR */
+- if (service->pcr_pid == 0x1fff && s->nb_streams > 0) {
+- pcr_st = s->streams[0];
+- ts_st = pcr_st->priv_data;
+- service->pcr_pid = ts_st->pid;
+- } else
+- ts_st = pcr_st->priv_data;
+-
+- if (ts->mux_rate > 1) {
+- service->pcr_packet_period = (int64_t)ts->mux_rate * ts->pcr_period /
+- (TS_PACKET_SIZE * 8 * 1000);
+- ts->sdt_packet_period = (int64_t)ts->mux_rate * SDT_RETRANS_TIME /
+- (TS_PACKET_SIZE * 8 * 1000);
+- ts->pat_packet_period = (int64_t)ts->mux_rate * PAT_RETRANS_TIME /
+- (TS_PACKET_SIZE * 8 * 1000);
+-
+- if (ts->copyts < 1)
+- ts->first_pcr = av_rescale(s->max_delay, PCR_TIME_BASE, AV_TIME_BASE);
+- } else {
+- /* Arbitrary values, PAT/PMT will also be written on video key frames */
+- ts->sdt_packet_period = 200;
+- ts->pat_packet_period = 40;
+- if (pcr_st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
+- int frame_size = av_get_audio_frame_duration2(pcr_st->codecpar, 0);
+- if (!frame_size) {
+- av_log(s, AV_LOG_WARNING, "frame size not set\n");
+- service->pcr_packet_period =
+- pcr_st->codecpar->sample_rate / (10 * 512);
+- } else {
+- service->pcr_packet_period =
+- pcr_st->codecpar->sample_rate / (10 * frame_size);
+- }
+- } else {
+- // max delta PCR 0.1s
+- // TODO: should be avg_frame_rate
+- service->pcr_packet_period =
+- ts_st->user_tb.den / (10 * ts_st->user_tb.num);
+- }
+- if (!service->pcr_packet_period)
+- service->pcr_packet_period = 1;
+- }
+-
+- ts->last_pat_ts = AV_NOPTS_VALUE;
+- ts->last_sdt_ts = AV_NOPTS_VALUE;
+- // The user specified a period, use only it
+- if (ts->pat_period < INT_MAX/2) {
+- ts->pat_packet_period = INT_MAX;
++ if (!pcr_st && s->nb_streams > 0)
++ pcr_st = s->streams[0];
++ if (!pcr_st) {
++ av_log(s, AV_LOG_ERROR, "no streams\n");
++ ret = AVERROR(EINVAL);
++ goto fail;
+ }
+- if (ts->sdt_period < INT_MAX/2) {
+- ts->sdt_packet_period = INT_MAX;
++ ts_st = pcr_st->priv_data;
++ if (service->pcr_sid == 0x1fff)
++ service->pcr_sid = ts_st->pid;
++ if (service->pcr_pid == 0x1fff)
++ service->pcr_pid = ts->m2ts_mode > 1 ?
++ 0x1000 + ts->service_id : service->pcr_sid ;
++ if (service->pmt.pid == service->pcr_pid) {
++ av_log(s, AV_LOG_ERROR, "Duplicate stream id %d\n", service->pcr_pid);
++ ret = AVERROR(EINVAL);
++ goto fail;
+ }
+
++ clk_rate = ts->mux_rate > 1 ? ts->mux_rate : PCR_TIME_BASE;
++ ts->sdt_packet_period = ts->sdt_period < 0 ? -1 : ts->sdt_period/1000 * clk_rate;
++ ts->pat_packet_period = ts->pat_period/1000 * clk_rate;
++ service->pcr_packet_period = ts->pcr_period/1000 * clk_rate;
++ if (service->pcr_packet_period < (TS_PACKET_SIZE*8*10))
++ service->pcr_packet_period = (TS_PACKET_SIZE*8*10);
++ av_log(s, AV_LOG_VERBOSE, "clk_rate %f: ticks/pkt %d pcr, %d sdt, %d pmt\n", clk_rate,
++ (int)service->pcr_packet_period, (int)ts->sdt_packet_period, (int)ts->pat_packet_period);
++
++ if (ts->copyts < 1)
++ ts->first_pcr = av_rescale(s->max_delay, PCR_TIME_BASE, AV_TIME_BASE);
++
+ // output a PCR as soon as possible
+- service->pcr_packet_count = service->pcr_packet_period;
+- ts->pat_packet_count = ts->pat_packet_period - 1;
+- ts->sdt_packet_count = ts->sdt_packet_period - 1;
++ ts->pcr = 0;
++ service->pcr_packet_timer = 0;
++ ts->pat_packet_timer = 0;
++ ts->sdt_packet_timer = 0;
+
+ if (ts->mux_rate == 1)
+ av_log(s, AV_LOG_VERBOSE, "muxrate VBR, ");
+ else
+ av_log(s, AV_LOG_VERBOSE, "muxrate %d, ", ts->mux_rate);
+- av_log(s, AV_LOG_VERBOSE,
+- "pcr every %d pkts, sdt every %d, pat/pmt every %d pkts\n",
+- service->pcr_packet_period,
+- ts->sdt_packet_period, ts->pat_packet_period);
+-
+- if (ts->m2ts_mode == -1) {
+- if (av_match_ext(s->url, "m2ts")) {
+- ts->m2ts_mode = 1;
+- } else {
+- ts->m2ts_mode = 0;
+- }
+- }
+
+ return 0;
+
+@@ -1032,22 +1003,12 @@
+ MpegTSWrite *ts = s->priv_data;
+ int i;
+
+- if (++ts->sdt_packet_count == ts->sdt_packet_period ||
+- (dts != AV_NOPTS_VALUE && ts->last_sdt_ts == AV_NOPTS_VALUE) ||
+- (dts != AV_NOPTS_VALUE && dts - ts->last_sdt_ts >= ts->sdt_period*90000.0)
+- ) {
+- ts->sdt_packet_count = 0;
+- if (dts != AV_NOPTS_VALUE)
+- ts->last_sdt_ts = FFMAX(dts, ts->last_sdt_ts);
++ if ( ts->sdt_packet_period >= 0 && ts->pcr >= ts->sdt_packet_timer ) {
++ ts->sdt_packet_timer = ts->pcr + ts->sdt_packet_period;
+ mpegts_write_sdt(s);
+ }
+- if (++ts->pat_packet_count == ts->pat_packet_period ||
+- (dts != AV_NOPTS_VALUE && ts->last_pat_ts == AV_NOPTS_VALUE) ||
+- (dts != AV_NOPTS_VALUE && dts - ts->last_pat_ts >= ts->pat_period*90000.0) ||
+- force_pat) {
+- ts->pat_packet_count = 0;
+- if (dts != AV_NOPTS_VALUE)
+- ts->last_pat_ts = FFMAX(dts, ts->last_pat_ts);
++ if (ts->pcr >= ts->pat_packet_timer || force_pat) {
++ ts->pat_packet_timer = ts->pcr + ts->pat_packet_period;
+ mpegts_write_pat(s);
+ for (i = 0; i < ts->nb_services; i++)
+ mpegts_write_pmt(s, ts->services[i]);
+@@ -1089,13 +1050,14 @@
+ {
+ MpegTSWrite *ts = s->priv_data;
+ MpegTSWriteStream *ts_st = st->priv_data;
++ uint32_t pcr_pid = ts_st->service->pcr_pid;
+ uint8_t *q;
+ uint8_t buf[TS_PACKET_SIZE];
+
+ q = buf;
+ *q++ = 0x47;
+- *q++ = ts_st->pid >> 8;
+- *q++ = ts_st->pid;
++ *q++ = pcr_pid >> 8;
++ *q++ = pcr_pid;
+ *q++ = 0x20 | ts_st->cc; /* Adaptation only */
+ /* Continuity Count field does not increment (see 13818-1 section 2.4.3.3) */
+ *q++ = TS_PACKET_SIZE - 5; /* Adaptation Field Length */
+@@ -1106,7 +1068,7 @@
+ }
+
+ /* PCR coded into 6 bytes */
+- q += write_pcr_bits(q, get_pcr(ts, s->pb));
++ q += write_pcr_bits(q, ts->pcr);
+
+ /* stuffing bytes */
+ memset(q, 0xFF, TS_PACKET_SIZE - (q - buf));
+@@ -1175,8 +1137,6 @@
+ uint8_t *q;
+ int val, is_start, len, header_len, write_pcr, is_dvb_subtitle, is_dvb_teletext, flags;
+ int afc_len, stuffing_len;
+- int64_t pcr = -1; /* avoid warning */
+- int64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE);
+ int force_pat = st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && key && !ts_st->prev_payload_key;
+
+ av_assert0(ts_st->payload != buf || st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO);
+@@ -1186,28 +1146,33 @@
+
+ is_start = 1;
+ while (payload_size > 0) {
++ ts->pcr = ts->first_pcr + (ts->mux_rate == 1 ?
++ (dts == AV_NOPTS_VALUE ? 0 : (dts - ts->delay) * 300) :
++ // add 11, pcr references the last byte of program clock reference base
++ av_rescale(avio_tell(s->pb) + 11, 8 * PCR_TIME_BASE, ts->mux_rate));
++
+ retransmit_si_info(s, force_pat, dts);
+ force_pat = 0;
+
+ write_pcr = 0;
+- if (ts_st->pid == ts_st->service->pcr_pid) {
+- if (ts->mux_rate > 1 || is_start) // VBR pcr period is based on frames
+- ts_st->service->pcr_packet_count++;
+- if (ts_st->service->pcr_packet_count >=
+- ts_st->service->pcr_packet_period) {
+- ts_st->service->pcr_packet_count = 0;
++ if (ts_st->pid == ts_st->service->pcr_sid) {
++ if( ts->pcr >= ts_st->service->pcr_packet_timer ) {
++ ts_st->service->pcr_packet_timer = ts->pcr + ts_st->service->pcr_packet_period;
+ write_pcr = 1;
+ }
+ }
+-
++ if (write_pcr && ts_st->service->pcr_sid != ts_st->service->pcr_pid) {
++ mpegts_insert_pcr_only(s, st);
++ continue;
++ }
+ if (ts->mux_rate > 1 && dts != AV_NOPTS_VALUE &&
+- (dts - get_pcr(ts, s->pb) / 300) > delay) {
+- /* pcr insert gets priority over null packet insert */
+- if (write_pcr)
+- mpegts_insert_pcr_only(s, st);
++ (dts - ts->pcr / 300) > ts->delay) {
++ /* pcr insert gets priority over null packet insert */
++ if (write_pcr)
++ mpegts_insert_pcr_only(s, st);
+ else
+- mpegts_insert_null_packet(s);
+- /* recalculate write_pcr and possibly retransmit si_info */
++ mpegts_insert_null_packet(s);
++ /* recalculate write_pcr and possibly retransimit si_info */
+ continue;
+ }
+
+@@ -1217,6 +1182,10 @@
+ val = ts_st->pid >> 8;
+ if (is_start)
+ val |= 0x40;
++ if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
++ st->codecpar->codec_id == AV_CODEC_ID_AC3 &&
++ ts->m2ts_mode > 1)
++ val |= 0x20;
+ *q++ = val;
+ *q++ = ts_st->pid;
+ ts_st->cc = ts_st->cc + 1 & 0xf;
+@@ -1228,7 +1197,7 @@
+ }
+ if (key && is_start && pts != AV_NOPTS_VALUE) {
+ // set Random Access for key frames
+- if (ts_st->pid == ts_st->service->pcr_pid)
++ if (ts_st->pid == ts_st->service->pcr_sid)
+ write_pcr = 1;
+ set_af_flag(buf, 0x40);
+ q = get_ts_payload_start(buf);
+@@ -1236,14 +1205,10 @@
+ if (write_pcr) {
+ set_af_flag(buf, 0x10);
+ q = get_ts_payload_start(buf);
+- // add 11, pcr references the last byte of program clock reference base
+ if (ts->mux_rate > 1)
+- pcr = get_pcr(ts, s->pb);
+- else
+- pcr = (dts - delay) * 300;
+- if (dts != AV_NOPTS_VALUE && dts < pcr / 300)
++ if (dts != AV_NOPTS_VALUE && dts < ts->pcr / 300)
+ av_log(s, AV_LOG_WARNING, "dts < pcr, TS is invalid\n");
+- extend_af(buf, write_pcr_bits(q, pcr));
++ extend_af(buf, write_pcr_bits(q, ts->pcr));
+ q = get_ts_payload_start(buf);
+ }
+ if (is_start) {
+@@ -1344,11 +1309,13 @@
+ *q++ = flags;
+ *q++ = header_len;
+ if (pts != AV_NOPTS_VALUE) {
+- write_pts(q, flags >> 6, pts);
++ int64_t ts_pts = pts + ts->ts_offset;
++ write_pts(q, flags >> 6, ts_pts);
+ q += 5;
+ }
+ if (dts != AV_NOPTS_VALUE && pts != AV_NOPTS_VALUE && dts != pts) {
+- write_pts(q, 1, dts);
++ int64_t ts_dts = dts + ts->ts_offset;
++ write_pts(q, 1, ts_dts);
+ q += 5;
+ }
+ if (pes_extension && st->codecpar->codec_id == AV_CODEC_ID_DIRAC) {
+@@ -1519,7 +1486,6 @@
+ uint8_t *data = NULL;
+ MpegTSWrite *ts = s->priv_data;
+ MpegTSWriteStream *ts_st = st->priv_data;
+- const int64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE) * 2;
+ int64_t dts = pkt->dts, pts = pkt->pts;
+ int opus_samples = 0;
+ int side_data_size;
+@@ -1540,16 +1506,15 @@
+ }
+
+ if (ts->flags & MPEGTS_FLAG_REEMIT_PAT_PMT) {
+- ts->pat_packet_count = ts->pat_packet_period - 1;
+- ts->sdt_packet_count = ts->sdt_packet_period - 1;
++ ts->pat_packet_timer = ts->sdt_packet_timer = 0;
+ ts->flags &= ~MPEGTS_FLAG_REEMIT_PAT_PMT;
+ }
+
+ if (ts->copyts < 1) {
+ if (pts != AV_NOPTS_VALUE)
+- pts += delay;
++ pts += 2*ts->delay;
+ if (dts != AV_NOPTS_VALUE)
+- dts += delay;
++ dts += 2*ts->delay;
+ }
+
+ if (ts_st->first_pts_check && pts == AV_NOPTS_VALUE) {
+@@ -1737,7 +1702,7 @@
+ AVStream *st2 = s->streams[i];
+ MpegTSWriteStream *ts_st2 = st2->priv_data;
+ if ( ts_st2->payload_size
+- && (ts_st2->payload_dts == AV_NOPTS_VALUE || dts - ts_st2->payload_dts > delay/2)) {
++ && (ts_st2->payload_dts == AV_NOPTS_VALUE || dts - ts_st2->payload_dts > ts->delay)) {
+ mpegts_write_pes(s, st2, ts_st2->payload, ts_st2->payload_size,
+ ts_st2->payload_pts, ts_st2->payload_dts,
+ ts_st2->payload_flags & AV_PKT_FLAG_KEY, stream_id);
+@@ -1908,12 +1873,18 @@
+ { "mpegts_pmt_start_pid", "Set the first pid of the PMT.",
+ offsetof(MpegTSWrite, pmt_start_pid), AV_OPT_TYPE_INT,
+ { .i64 = 0x1000 }, 0x0010, 0x1f00, AV_OPT_FLAG_ENCODING_PARAM },
++ { "mpegts_pcr_start_pid", "Set the first pid of the PCR.",
++ offsetof(MpegTSWrite, pcr_start_pid), AV_OPT_TYPE_INT,
++ { .i64 = 0x1000 }, 0x0010, 0x1f00, AV_OPT_FLAG_ENCODING_PARAM },
+ { "mpegts_start_pid", "Set the first pid.",
+ offsetof(MpegTSWrite, start_pid), AV_OPT_TYPE_INT,
+ { .i64 = 0x0100 }, 0x0010, 0x0f00, AV_OPT_FLAG_ENCODING_PARAM },
+ { "mpegts_m2ts_mode", "Enable m2ts mode.",
+ offsetof(MpegTSWrite, m2ts_mode), AV_OPT_TYPE_BOOL,
+- { .i64 = -1 }, -1, 1, AV_OPT_FLAG_ENCODING_PARAM },
++ { .i64 = -1 }, -1, 2, AV_OPT_FLAG_ENCODING_PARAM },
++ { "mpegts_pcr_offset", "clock offset.",
++ offsetof(MpegTSWrite, ts_offset), AV_OPT_TYPE_BOOL,
++ { .i64 = 0 }, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+ { "muxrate", NULL,
+ offsetof(MpegTSWrite, mux_rate), AV_OPT_TYPE_INT,
+ { .i64 = 1 }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+@@ -1951,15 +1922,15 @@
+ { "omit_video_pes_length", "Omit the PES packet length for video packets",
+ offsetof(MpegTSWrite, omit_video_pes_length), AV_OPT_TYPE_BOOL,
+ { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
+- { "pcr_period", "PCR retransmission time in milliseconds",
+- offsetof(MpegTSWrite, pcr_period), AV_OPT_TYPE_INT,
+- { .i64 = PCR_RETRANS_TIME }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+- { "pat_period", "PAT/PMT retransmission time limit in seconds",
++ { "pcr_period", "PCR retransmission time limit in msecs",
++ offsetof(MpegTSWrite, pcr_period), AV_OPT_TYPE_DOUBLE,
++ { .dbl = PCR_RETRANS_TIME }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
++ { "pat_period", "PAT/PMT retransmission time limit in msecs",
+ offsetof(MpegTSWrite, pat_period), AV_OPT_TYPE_DOUBLE,
+- { .dbl = INT_MAX }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+- { "sdt_period", "SDT retransmission time limit in seconds",
++ { .dbl = PAT_RETRANS_TIME }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
++ { "sdt_period", "SDT retransmission time limit in msecs",
+ offsetof(MpegTSWrite, sdt_period), AV_OPT_TYPE_DOUBLE,
+- { .dbl = INT_MAX }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
++ { .dbl = SDT_RETRANS_TIME }, -1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
+ { NULL },
+ };
+
--- /dev/null
+diff -urN a/libavformat/avformat.h b/libavformat/avformat.h
+--- a/libavformat/avformat.h 2018-11-05 16:22:26.000000000 -0700
++++ b/libavformat/avformat.h 2018-11-08 07:25:17.066799941 -0700
+@@ -487,6 +487,9 @@
+ The user or muxer can override this through
+ AVFormatContext.avoid_negative_ts
+ */
++#define AVFMT_SEEK_NOSTREAMS 0x80000 /**< Stream index ignored by seek,
++ or some streams fail to seek
++ */
+
+ #define AVFMT_SEEK_TO_PTS 0x4000000 /**< Seeking is based on PTS */
+
+@@ -647,7 +650,8 @@
+ /**
+ * Can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER, AVFMT_SHOW_IDS,
+ * AVFMT_GENERIC_INDEX, AVFMT_TS_DISCONT, AVFMT_NOBINSEARCH,
+- * AVFMT_NOGENSEARCH, AVFMT_NO_BYTE_SEEK, AVFMT_SEEK_TO_PTS.
++ * AVFMT_NOGENSEARCH, AVFMT_NO_BYTE_SEEK, AVFMT_SEEK_TO_PTS,
++ * AVFMT_SEEK_NOSTREAMS
+ */
+ int flags;
+
+diff -urN a/libavformat/dv.c b/libavformat/dv.c
+--- a/libavformat/dv.c 2018-11-01 12:34:26.000000000 -0600
++++ b/libavformat/dv.c 2018-11-08 07:25:17.066799941 -0700
+@@ -632,6 +632,7 @@
+ AVInputFormat ff_dv_demuxer = {
+ .name = "dv",
+ .long_name = NULL_IF_CONFIG_SMALL("DV (Digital Video)"),
++ .flags = AVFMT_SEEK_NOSTREAMS,
+ .priv_data_size = sizeof(RawDVContext),
+ .read_probe = dv_probe,
+ .read_header = dv_read_header,
+diff -urN a/libavformat/matroskadec.c b/libavformat/matroskadec.c
+--- a/libavformat/matroskadec.c 2018-11-05 16:22:26.000000000 -0700
++++ b/libavformat/matroskadec.c 2018-11-08 07:25:17.067799930 -0700
+@@ -4030,6 +4030,7 @@
+ AVInputFormat ff_matroska_demuxer = {
+ .name = "matroska,webm",
+ .long_name = NULL_IF_CONFIG_SMALL("Matroska / WebM"),
++ .flags = AVFMT_SEEK_NOSTREAMS,
+ .extensions = "mkv,mk3d,mka,mks",
+ .priv_data_size = sizeof(MatroskaDemuxContext),
+ .read_probe = matroska_probe,
+@@ -4043,6 +4044,7 @@
+ AVInputFormat ff_webm_dash_manifest_demuxer = {
+ .name = "webm_dash_manifest",
+ .long_name = NULL_IF_CONFIG_SMALL("WebM DASH Manifest"),
++ .flags = AVFMT_SEEK_NOSTREAMS,
+ .priv_data_size = sizeof(MatroskaDemuxContext),
+ .read_header = webm_dash_manifest_read_header,
+ .read_packet = webm_dash_manifest_read_packet,
+diff -urN a/libavformat/utils.c b/libavformat/utils.c
+--- a/libavformat/utils.c 2018-11-05 16:22:26.000000000 -0700
++++ b/libavformat/utils.c 2018-11-08 07:25:17.069799908 -0700
+@@ -2472,6 +2472,13 @@
+ return seek_frame_byte(s, stream_index, timestamp, flags);
+ }
+
++ if (stream_index != -1 && (s->iformat->flags & AVFMT_SEEK_NOSTREAMS)) {
++ timestamp = av_rescale_q(timestamp,
++ s->streams[stream_index]->time_base,
++ AV_TIME_BASE_Q);
++ stream_index = -1;
++ }
++
+ if (stream_index < 0) {
+ stream_index = av_find_default_stream_index(s);
+ if (stream_index < 0)
diff -urN a/libavformat/avformat.h b/libavformat/avformat.h
---- a/libavformat/avformat.h 2018-04-20 04:02:57.000000000 -0600
-+++ b/libavformat/avformat.h 2018-04-24 11:02:20.777232001 -0600
+--- a/libavformat/avformat.h 2018-11-05 16:22:26.000000000 -0700
++++ b/libavformat/avformat.h 2018-11-08 07:25:17.066799941 -0700
@@ -487,6 +487,9 @@
The user or muxer can override this through
AVFormatContext.avoid_negative_ts
int flags;
diff -urN a/libavformat/dv.c b/libavformat/dv.c
---- a/libavformat/dv.c 2018-04-13 17:34:28.000000000 -0600
-+++ b/libavformat/dv.c 2018-04-24 11:02:20.778232001 -0600
+--- a/libavformat/dv.c 2018-11-01 12:34:26.000000000 -0600
++++ b/libavformat/dv.c 2018-11-08 07:25:17.066799941 -0700
@@ -632,6 +632,7 @@
AVInputFormat ff_dv_demuxer = {
.name = "dv",
.read_probe = dv_probe,
.read_header = dv_read_header,
diff -urN a/libavformat/matroskadec.c b/libavformat/matroskadec.c
---- a/libavformat/matroskadec.c 2018-04-20 04:02:57.000000000 -0600
-+++ b/libavformat/matroskadec.c 2018-04-24 11:02:20.779232001 -0600
-@@ -4026,6 +4026,7 @@
+--- a/libavformat/matroskadec.c 2018-11-05 16:22:26.000000000 -0700
++++ b/libavformat/matroskadec.c 2018-11-08 07:25:17.067799930 -0700
+@@ -4030,6 +4030,7 @@
AVInputFormat ff_matroska_demuxer = {
.name = "matroska,webm",
.long_name = NULL_IF_CONFIG_SMALL("Matroska / WebM"),
.extensions = "mkv,mk3d,mka,mks",
.priv_data_size = sizeof(MatroskaDemuxContext),
.read_probe = matroska_probe,
-@@ -4039,6 +4040,7 @@
+@@ -4043,6 +4044,7 @@
AVInputFormat ff_webm_dash_manifest_demuxer = {
.name = "webm_dash_manifest",
.long_name = NULL_IF_CONFIG_SMALL("WebM DASH Manifest"),
.read_header = webm_dash_manifest_read_header,
.read_packet = webm_dash_manifest_read_packet,
diff -urN a/libavformat/utils.c b/libavformat/utils.c
---- a/libavformat/utils.c 2018-04-20 04:02:58.000000000 -0600
-+++ b/libavformat/utils.c 2018-04-24 11:02:20.780232001 -0600
-@@ -2471,6 +2471,13 @@
+--- a/libavformat/utils.c 2018-11-05 16:22:26.000000000 -0700
++++ b/libavformat/utils.c 2018-11-08 07:25:17.069799908 -0700
+@@ -2472,6 +2472,13 @@
return seek_frame_byte(s, stream_index, timestamp, flags);
}