Cleanup: left wrong comment in
[blender-staging.git] / source / blender / editors / space_sequencer / sequencer_edit.c
index c48171826a9d00a3714c276b389f96743ad6e882..0895d28fba75fe7bd86f0997029e739c9ff4e4a8 100644 (file)
 #include "MEM_guardedalloc.h"
 
 #include "BLI_blenlib.h"
+#include "BLI_ghash.h"
 #include "BLI_math.h"
+#include "BLI_timecode.h"
 #include "BLI_utildefines.h"
 
-#include "BLF_translation.h"
+#include "BLT_translation.h"
 
 #include "DNA_scene_types.h"
+#include "DNA_sound_types.h"
 
 #include "BKE_context.h"
 #include "BKE_global.h"
 
 /* for menu/popup icons etc etc*/
 
+#include "ED_anim_api.h"
+#include "ED_numinput.h"
 #include "ED_screen.h"
 #include "ED_transform.h"
 #include "ED_sequencer.h"
 #include "ED_space_api.h"
 
 #include "UI_view2d.h"
+#include "UI_interface.h"
 
 
 /* own include */
@@ -88,6 +94,8 @@ EnumPropertyItem sequencer_prop_effect_types[] = {
        {SEQ_TYPE_MULTICAM, "MULTICAM", 0, "Multicam Selector", ""},
        {SEQ_TYPE_ADJUSTMENT, "ADJUSTMENT", 0, "Adjustment Layer", ""},
        {SEQ_TYPE_GAUSSIAN_BLUR, "GAUSSIAN_BLUR", 0, "Gaussian Blur", ""},
+       {SEQ_TYPE_TEXT, "TEXT", 0, "Text", ""},
+       {SEQ_TYPE_COLORMIX, "COLORMIX", 0, "Color Mix", ""},
        {0, NULL, 0, NULL, NULL}
 };
 
@@ -100,7 +108,7 @@ EnumPropertyItem prop_side_types[] = {
        {0, NULL, 0, NULL, NULL}
 };
 
-static EnumPropertyItem prop_side_lr_types[] = {
+static const EnumPropertyItem prop_side_lr_types[] = {
        {SEQ_SIDE_LEFT, "LEFT", 0, "Left", ""},
        {SEQ_SIDE_RIGHT, "RIGHT", 0, "Right", ""},
        {0, NULL, 0, NULL, NULL}
@@ -146,11 +154,12 @@ static void proxy_startjob(void *pjv, short *stop, short *do_update, float *prog
                struct SeqIndexBuildContext *context = link->data;
 
                BKE_sequencer_proxy_rebuild(context, stop, do_update, progress);
-       }
-
-       if (*stop) {
-               pj->stop = 1;
-               fprintf(stderr,  "Canceling proxy rebuild on users request...\n");
+               
+               if (*stop) {
+                       pj->stop = 1;
+                       fprintf(stderr,  "Canceling proxy rebuild on users request...\n");
+                       break;
+               }
        }
 }
 
@@ -176,9 +185,12 @@ static void seq_proxy_build_job(const bContext *C)
        Scene *scene = CTX_data_scene(C);
        Editing *ed = BKE_sequencer_editing_get(scene, false);
        ScrArea *sa = CTX_wm_area(C);
-       struct SeqIndexBuildContext *context;
-       LinkData *link;
        Sequence *seq;
+       GSet *file_list;
+       
+       if (ed == NULL) {
+               return;
+       }
 
        wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), sa, "Building Proxies",
                             WM_JOB_PROGRESS, WM_JOB_TYPE_SEQ_BUILD_PROXY);
@@ -196,16 +208,17 @@ static void seq_proxy_build_job(const bContext *C)
                WM_jobs_callbacks(wm_job, proxy_startjob, NULL, NULL, proxy_endjob);
        }
 
+       file_list = BLI_gset_new(BLI_ghashutil_strhash_p, BLI_ghashutil_strcmp, "file list");
        SEQP_BEGIN (ed, seq)
        {
                if ((seq->flag & SELECT)) {
-                       context = BKE_sequencer_proxy_rebuild_context(pj->main, pj->scene, seq);
-                       link = BLI_genericNodeN(context);
-                       BLI_addtail(&pj->queue, link);
+                       BKE_sequencer_proxy_rebuild_context(pj->main, pj->scene, seq, file_list, &pj->queue);
                }
        }
        SEQ_END
 
+       BLI_gset_free(file_list, MEM_freeN);
+       
        if (!WM_jobs_is_running(wm_job)) {
                G.is_break = false;
                WM_jobs_start(CTX_wm_manager(C), wm_job);
@@ -495,6 +508,13 @@ bool ED_space_sequencer_check_show_imbuf(SpaceSeq *sseq)
                ELEM(sseq->mainb, SEQ_DRAW_SEQUENCE, SEQ_DRAW_IMG_IMBUF));
 }
 
+bool ED_space_sequencer_check_show_strip(SpaceSeq *sseq)
+{
+       return (ELEM(sseq->view, SEQ_VIEW_SEQUENCE, SEQ_VIEW_SEQUENCE_PREVIEW) &&
+               ELEM(sseq->mainb, SEQ_DRAW_SEQUENCE, SEQ_DRAW_IMG_IMBUF));
+}
+
+
 int seq_effect_find_selected(Scene *scene, Sequence *activeseq, int type, Sequence **selseq1, Sequence **selseq2, Sequence **selseq3, const char **error_str)
 {
        Editing *ed = BKE_sequencer_editing_get(scene, false);
@@ -543,7 +563,7 @@ int seq_effect_find_selected(Scene *scene, Sequence *activeseq, int type, Sequen
                        }
                        if (seq1 == NULL) seq1 = seq2;
                        if (seq3 == NULL) seq3 = seq2;
-                       /* fall-through */
+                       ATTR_FALLTHROUGH;
                case 2:
                        if (seq1 == NULL || seq2 == NULL) {
                                *error_str = N_("2 selected sequence strips are needed");
@@ -651,6 +671,9 @@ static Sequence *cut_seq_hard(Scene *scene, Sequence *seq, int cutframe)
        Sequence *seqn = NULL;
        bool skip_dup = false;
 
+       /* Unlike soft-cut, it's important to use the same value for both strips. */
+       const bool is_end_exact = ((seq->start + seq->len) == cutframe);
+
        /* backup values */
        ts.start = seq->start;
        ts.machine = seq->machine;
@@ -663,11 +686,21 @@ static Sequence *cut_seq_hard(Scene *scene, Sequence *seq, int cutframe)
        ts.anim_startofs = seq->anim_startofs;
        ts.anim_endofs = seq->anim_endofs;
        ts.len = seq->len;
-       
+
        /* First Strip! */
        /* strips with extended stillfames before */
        
-       if ((seq->startstill) && (cutframe < seq->start)) {
+       /* Precaution, needed because the length saved on-disk may not match the length saved in the blend file,
+        * or our code may have minor differences reading file length between versions.
+        * This causes hard-cut to fail, see: T47862 */
+       if (seq->type != SEQ_TYPE_META) {
+               BKE_sequence_reload_new_file(scene, seq, true);
+               BKE_sequence_calc(scene, seq);
+       }
+
+       /* Important to offset the start when 'cutframe == seq->start'
+        * because we need at least one frame of content after start/end still have clipped it. */
+       if ((seq->startstill) && (cutframe <= seq->start)) {
                /* don't do funny things with METAs ... */
                if (seq->type == SEQ_TYPE_META) {
                        skip_dup = true;
@@ -681,13 +714,17 @@ static Sequence *cut_seq_hard(Scene *scene, Sequence *seq, int cutframe)
                }
        }
        /* normal strip */
-       else if ((cutframe >= seq->start) && (cutframe <= (seq->start + seq->len))) {
+       else if ((is_end_exact == false) &&
+                ((cutframe >= seq->start) && (cutframe <= (seq->start + seq->len))))
+       {
                seq->endofs = 0;
                seq->endstill = 0;
                seq->anim_endofs += (seq->start + seq->len) - cutframe;
        }
        /* strips with extended stillframes after */
-       else if (((seq->start + seq->len) < cutframe) && (seq->endstill)) {
+       else if ((is_end_exact == true) ||
+                (((seq->start + seq->len) < cutframe) && (seq->endstill)))
+       {
                seq->endstill -= seq->enddisp - cutframe;
                /* don't do funny things with METAs ... */
                if (seq->type == SEQ_TYPE_META) {
@@ -700,12 +737,16 @@ static Sequence *cut_seq_hard(Scene *scene, Sequence *seq, int cutframe)
 
        if (!skip_dup) {
                /* Duplicate AFTER the first change */
-               seqn = BKE_sequence_dupli_recursive(scene, NULL, seq, SEQ_DUPE_UNIQUE_NAME | SEQ_DUPE_ANIM);
+               seqn = BKE_sequence_dupli_recursive(scene, scene, seq, SEQ_DUPE_UNIQUE_NAME | SEQ_DUPE_ANIM);
        }
        
        if (seqn) {
                seqn->flag |= SELECT;
-                       
+
+               /* Important not to re-assign this (unlike soft-cut) */
+#if 0
+               is_end_exact = ((seqn->start + seqn->len) == cutframe);
+#endif
                /* Second Strip! */
                /* strips with extended stillframes before */
                if ((seqn->startstill) && (cutframe == seqn->start + 1)) {
@@ -714,9 +755,11 @@ static Sequence *cut_seq_hard(Scene *scene, Sequence *seq, int cutframe)
                        seqn->anim_endofs = ts.anim_endofs;
                        seqn->endstill = ts.endstill;
                }
-               
+
                /* normal strip */
-               else if ((cutframe >= seqn->start) && (cutframe <= (seqn->start + seqn->len))) {
+               else if ((is_end_exact == false) &&
+                        ((cutframe >= seqn->start) && (cutframe <= (seqn->start + seqn->len))))
+               {
                        seqn->start = cutframe;
                        seqn->startstill = 0;
                        seqn->startofs = 0;
@@ -725,16 +768,18 @@ static Sequence *cut_seq_hard(Scene *scene, Sequence *seq, int cutframe)
                        seqn->anim_endofs = ts.anim_endofs;
                        seqn->endstill = ts.endstill;
                }
-               
+
                /* strips with extended stillframes after */
-               else if (((seqn->start + seqn->len) < cutframe) && (seqn->endstill)) {
+               else if ((is_end_exact == true) ||
+                        (((seqn->start + seqn->len) < cutframe) && (seqn->endstill)))
+               {
                        seqn->start = cutframe;
                        seqn->startofs = 0;
                        seqn->anim_startofs += ts.len - 1;
                        seqn->endstill = ts.enddisp - cutframe - 1;
                        seqn->startstill = 0;
                }
-               
+
                BKE_sequence_reload_new_file(scene, seqn, false);
                BKE_sequence_calc(scene, seqn);
        }
@@ -747,6 +792,8 @@ static Sequence *cut_seq_soft(Scene *scene, Sequence *seq, int cutframe)
        Sequence *seqn = NULL;
        bool skip_dup = false;
 
+       bool is_end_exact = ((seq->start + seq->len) == cutframe);
+
        /* backup values */
        ts.start = seq->start;
        ts.machine = seq->machine;
@@ -759,11 +806,13 @@ static Sequence *cut_seq_soft(Scene *scene, Sequence *seq, int cutframe)
        ts.anim_startofs = seq->anim_startofs;
        ts.anim_endofs = seq->anim_endofs;
        ts.len = seq->len;
-       
+
        /* First Strip! */
        /* strips with extended stillfames before */
-       
-       if ((seq->startstill) && (cutframe < seq->start)) {
+
+       /* Important to offset the start when 'cutframe == seq->start'
+        * because we need at least one frame of content after start/end still have clipped it. */
+       if ((seq->startstill) && (cutframe <= seq->start)) {
                /* don't do funny things with METAs ... */
                if (seq->type == SEQ_TYPE_META) {
                        skip_dup = true;
@@ -777,11 +826,15 @@ static Sequence *cut_seq_soft(Scene *scene, Sequence *seq, int cutframe)
                }
        }
        /* normal strip */
-       else if ((cutframe >= seq->start) && (cutframe <= (seq->start + seq->len))) {
+       else if ((is_end_exact == false) &&
+                (cutframe >= seq->start) && (cutframe <= (seq->start + seq->len)))
+       {
                seq->endofs = (seq->start + seq->len) - cutframe;
        }
        /* strips with extended stillframes after */
-       else if (((seq->start + seq->len) < cutframe) && (seq->endstill)) {
+       else if ((is_end_exact == true) ||
+                (((seq->start + seq->len) < cutframe) && (seq->endstill)))
+       {
                seq->endstill -= seq->enddisp - cutframe;
                /* don't do funny things with METAs ... */
                if (seq->type == SEQ_TYPE_META) {
@@ -793,12 +846,14 @@ static Sequence *cut_seq_soft(Scene *scene, Sequence *seq, int cutframe)
 
        if (!skip_dup) {
                /* Duplicate AFTER the first change */
-               seqn = BKE_sequence_dupli_recursive(scene, NULL, seq, SEQ_DUPE_UNIQUE_NAME | SEQ_DUPE_ANIM);
+               seqn = BKE_sequence_dupli_recursive(scene, scene, seq, SEQ_DUPE_UNIQUE_NAME | SEQ_DUPE_ANIM);
        }
        
        if (seqn) {
                seqn->flag |= SELECT;
-                       
+
+               is_end_exact = ((seqn->start + seqn->len) == cutframe);
+
                /* Second Strip! */
                /* strips with extended stillframes before */
                if ((seqn->startstill) && (cutframe == seqn->start + 1)) {
@@ -807,23 +862,27 @@ static Sequence *cut_seq_soft(Scene *scene, Sequence *seq, int cutframe)
                        seqn->endofs = ts.endofs;
                        seqn->endstill = ts.endstill;
                }
-               
+
                /* normal strip */
-               else if ((cutframe >= seqn->start) && (cutframe <= (seqn->start + seqn->len))) {
+               else if ((is_end_exact == false) &&
+                        (cutframe >= seqn->start) && (cutframe <= (seqn->start + seqn->len)))
+               {
                        seqn->startstill = 0;
                        seqn->startofs = cutframe - ts.start;
                        seqn->endofs = ts.endofs;
                        seqn->endstill = ts.endstill;
                }
-               
+
                /* strips with extended stillframes after */
-               else if (((seqn->start + seqn->len) < cutframe) && (seqn->endstill)) {
+               else if ((is_end_exact == true) ||
+                        (((seqn->start + seqn->len) < cutframe) && (seqn->endstill)))
+               {
                        seqn->start = cutframe - ts.len + 1;
                        seqn->startofs = ts.len - 1;
                        seqn->endstill = ts.enddisp - cutframe - 1;
                        seqn->startstill = 0;
                }
-               
+
                BKE_sequence_calc(scene, seqn);
        }
        return seqn;
@@ -885,6 +944,7 @@ static bool sequence_offset_after_frame(Scene *scene, const int delta, const int
        Sequence *seq;
        Editing *ed = BKE_sequencer_editing_get(scene, false);
        bool done = false;
+       TimeMarker *marker;
 
        /* all strips >= cfra are shifted */
        
@@ -898,38 +958,15 @@ static bool sequence_offset_after_frame(Scene *scene, const int delta, const int
                }
        }
 
-       return done;
-}
-
-static void UNUSED_FUNCTION(touch_seq_files) (Scene *scene)
-{
-       Sequence *seq;
-       Editing *ed = BKE_sequencer_editing_get(scene, false);
-       char str[256];
-
-       /* touch all strips with movies */
-       
-       if (ed == NULL) return;
-
-       // XXX25 if (okee("Touch and print selected movies")==0) return;
-
-       WM_cursor_wait(1);
-
-       SEQP_BEGIN (ed, seq)
-       {
-               if (seq->flag & SELECT) {
-                       if (seq->type == SEQ_TYPE_MOVIE) {
-                               if (seq->strip && seq->strip->stripdata) {
-                                       BLI_make_file_string(G.main->name, str, seq->strip->dir, seq->strip->stripdata->name);
-                                       BLI_file_touch(seq->name);
-                               }
+       if (!scene->toolsettings->lock_markers) {
+               for (marker = scene->markers.first; marker; marker = marker->next) {
+                       if (marker->frame >= cfra) {
+                               marker->frame += delta;
                        }
-
                }
        }
-       SEQ_END
 
-       WM_cursor_wait(0);
+       return done;
 }
 
 #if 0
@@ -969,20 +1006,20 @@ static void UNUSED_FUNCTION(seq_remap_paths) (Scene *scene)
                return;
        
        BLI_strncpy(from, last_seq->strip->dir, sizeof(from));
-// XXX if (0 == sbutton(from, 0, sizeof(from)-1, "From: "))
+// XXX if (0 == sbutton(from, 0, sizeof(from) - 1, "From: "))
 //             return;
        
        BLI_strncpy(to, from, sizeof(to));
-// XXX if (0 == sbutton(to, 0, sizeof(to)-1, "To: "))
+// XXX if (0 == sbutton(to, 0, sizeof(to) - 1, "To: "))
 //             return;
        
-       if (strcmp(to, from) == 0)
+       if (STREQ(to, from))
                return;
        
        SEQP_BEGIN (ed, seq)
        {
                if (seq->flag & SELECT) {
-                       if (strncmp(seq->strip->dir, from, strlen(from)) == 0) {
+                       if (STREQLEN(seq->strip->dir, from, strlen(from))) {
                                printf("found %s\n", seq->strip->dir);
                                
                                /* strip off the beginning */
@@ -1020,7 +1057,7 @@ static int sequencer_gap_remove_exec(bContext *C, wmOperator *op)
                        break;
                }
        }
-       
+
        for ( ; cfra < efra; cfra++) {
                /* first == 0 means there's still no strip to remove a gap for */
                if (first == false) {
@@ -1130,7 +1167,7 @@ int sequencer_strip_has_path_poll(bContext *C)
        return (((ed = BKE_sequencer_editing_get(CTX_data_scene(C), false)) != NULL) && ((seq = ed->act_seq) != NULL) && (SEQ_HAS_PATH(seq)));
 }
 
-int sequencer_view_poll(bContext *C)
+int sequencer_view_preview_poll(bContext *C)
 {
        SpaceSeq *sseq = CTX_wm_space_seq(C);
        Editing *ed = BKE_sequencer_editing_get(CTX_data_scene(C), false);
@@ -1140,6 +1177,15 @@ int sequencer_view_poll(bContext *C)
        return 0;
 }
 
+int sequencer_view_strips_poll(bContext *C)
+{
+       SpaceSeq *sseq = CTX_wm_space_seq(C);
+       if (sseq && ED_space_sequencer_check_show_strip(sseq))
+               return 1;
+
+       return 0;
+}
+
 /* snap operator*/
 static int sequencer_snap_exec(bContext *C, wmOperator *op)
 {
@@ -1170,6 +1216,7 @@ static int sequencer_snap_exec(bContext *C, wmOperator *op)
                                        BKE_sequence_tx_set_final_right(seq, snap_frame);
                                }
                                BKE_sequence_tx_handle_xlimits(seq, seq->flag & SEQ_LEFTSEL, seq->flag & SEQ_RIGHTSEL);
+                               BKE_sequence_single_fix(seq);
                        }
                        BKE_sequence_calc(scene, seq);
                }
@@ -1217,7 +1264,7 @@ static int sequencer_snap_invoke(bContext *C, wmOperator *op, const wmEvent *UNU
 void SEQUENCER_OT_snap(struct wmOperatorType *ot)
 {
        /* identifiers */
-       ot->name = "Snap Strips";
+       ot->name = "Snap Strips to Frame";
        ot->idname = "SEQUENCER_OT_snap";
        ot->description = "Frame where selected strips will be snapped";
        
@@ -1242,6 +1289,7 @@ typedef struct SlipData {
        bool slow;
        int slow_offset; /* offset at the point where offset was turned on */
        void *draw_handle;
+       NumInput num_input;
 } SlipData;
 
 static void transseq_backup(TransSeq *ts, Sequence *seq)
@@ -1355,6 +1403,13 @@ static int sequencer_slip_invoke(bContext *C, wmOperator *op, const wmEvent *eve
        data->trim = MEM_mallocN(num_seq * sizeof(bool), "trimdata_trim");
        data->num_seq = num_seq;
 
+       initNumInput(&data->num_input);
+       data->num_input.idx_max = 0;
+       data->num_input.val_flag[0] |= NUM_NO_FRACTION;
+       data->num_input.unit_sys = USER_UNIT_NONE;
+       data->num_input.unit_type[0] = 0;
+
+
        slip_add_sequences_rec(ed->seqbasep, data->seq_array, data->trim, 0, true);
 
        for (i = 0; i < num_seq; i++) {
@@ -1480,52 +1535,87 @@ static int sequencer_slip_exec(bContext *C, wmOperator *op)
        }
 }
 
+
+static void sequencer_slip_update_header(Scene *scene, ScrArea *sa, SlipData *data, int offset)
+{
+       char msg[UI_MAX_DRAW_STR];
+
+       if (sa) {
+               if (hasNumInput(&data->num_input)) {
+                       char num_str[NUM_STR_REP_LEN];
+                       outputNumInput(&data->num_input, num_str, &scene->unit);
+                       BLI_snprintf(msg, sizeof(msg), IFACE_("Trim offset: %s"), num_str);
+               }
+               else {
+                       BLI_snprintf(msg, sizeof(msg), IFACE_("Trim offset: %d"), offset);
+               }
+       }
+
+       ED_area_headerprint(sa, msg);
+}
+
 static int sequencer_slip_modal(bContext *C, wmOperator *op, const wmEvent *event)
 {
        Scene *scene = CTX_data_scene(C);
        SlipData *data = (SlipData *)op->customdata;
        ScrArea *sa = CTX_wm_area(C);
        ARegion *ar = CTX_wm_region(C);
+       const bool has_numInput = hasNumInput(&data->num_input);
+       bool handled = true;
+
+       /* Modal numinput active, try to handle numeric inputs first... */
+       if (event->val == KM_PRESS && has_numInput && handleNumInput(C, &data->num_input, event)) {
+               float offset;
+               applyNumInput(&data->num_input, &offset);
+
+               sequencer_slip_update_header(scene, sa, data, (int)offset);
+
+               RNA_int_set(op->ptr, "offset", offset);
+
+               if (sequencer_slip_recursively(scene, data, offset)) {
+                       WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
+               }
+
+               return OPERATOR_RUNNING_MODAL;
+       }
 
        switch (event->type) {
                case MOUSEMOVE:
                {
-                       float mouseloc[2];
-                       int offset;
-                       int mouse_x;
-                       View2D *v2d = UI_view2d_fromcontext(C);
-
-                       if (data->slow) {
-                               mouse_x = event->mval[0] - data->slow_offset;
-                               mouse_x *= 0.1f;
-                               mouse_x += data->slow_offset;
-                       }
-                       else {
-                               mouse_x = event->mval[0];
-                       }
+                       if (!has_numInput) {
+                               float mouseloc[2];
+                               int offset;
+                               int mouse_x;
+                               View2D *v2d = UI_view2d_fromcontext(C);
+
+                               if (data->slow) {
+                                       mouse_x = event->mval[0] - data->slow_offset;
+                                       mouse_x *= 0.1f;
+                                       mouse_x += data->slow_offset;
+                               }
+                               else {
+                                       mouse_x = event->mval[0];
+                               }
 
 
-                       /* choose the side based on which side of the playhead the mouse is on */
-                       UI_view2d_region_to_view(v2d, mouse_x, 0, &mouseloc[0], &mouseloc[1]);
-                       offset = mouseloc[0] - data->init_mouseloc[0];
+                               /* choose the side based on which side of the playhead the mouse is on */
+                               UI_view2d_region_to_view(v2d, mouse_x, 0, &mouseloc[0], &mouseloc[1]);
+                               offset = mouseloc[0] - data->init_mouseloc[0];
 
-                       RNA_int_set(op->ptr, "offset", offset);
+                               sequencer_slip_update_header(scene, sa, data, offset);
 
-                       if (sa) {
-#define HEADER_LENGTH 40
-                               char msg[HEADER_LENGTH];
-                               BLI_snprintf(msg, HEADER_LENGTH, "Trim offset: %d", offset);
-#undef HEADER_LENGTH
-                               ED_area_headerprint(sa, msg);
-                       }
+                               RNA_int_set(op->ptr, "offset", offset);
 
-                       if (sequencer_slip_recursively(scene, data, offset)) {
-                               WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
+                               if (sequencer_slip_recursively(scene, data, offset)) {
+                                       WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
+                               }
                        }
                        break;
                }
 
                case LEFTMOUSE:
+               case RETKEY:
+               case SPACEKEY:
                {
                        ED_region_draw_cb_exit(ar->type, data->draw_handle);
                        MEM_freeN(data->seq_array);
@@ -1577,19 +1667,36 @@ static int sequencer_slip_modal(bContext *C, wmOperator *op, const wmEvent *even
 
                case RIGHTSHIFTKEY:
                case LEFTSHIFTKEY:
-                       if (event->val == KM_PRESS) {
-                               data->slow = true;
-                               data->slow_offset = event->mval[0];
-                       }
-                       else if (event->val == KM_RELEASE) {
-                               data->slow = false;
+                       if (!has_numInput) {
+                               if (event->val == KM_PRESS) {
+                                       data->slow = true;
+                                       data->slow_offset = event->mval[0];
+                               }
+                               else if (event->val == KM_RELEASE) {
+                                       data->slow = false;
+                               }
                        }
                        break;
 
                default:
+                       handled = false;
                        break;
        }
 
+       /* Modal numinput inactive, try to handle numeric inputs last... */
+       if (!handled && event->val == KM_PRESS && handleNumInput(C, &data->num_input, event)) {
+               float offset;
+               applyNumInput(&data->num_input, &offset);
+
+               sequencer_slip_update_header(scene, sa, data, (int)offset);
+
+               RNA_int_set(op->ptr, "offset", offset);
+
+               if (sequencer_slip_recursively(scene, data, offset)) {
+                       WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
+               }
+       }
+
        return OPERATOR_RUNNING_MODAL;
 }
 
@@ -1651,7 +1758,7 @@ void SEQUENCER_OT_mute(struct wmOperatorType *ot)
        /* identifiers */
        ot->name = "Mute Strips";
        ot->idname = "SEQUENCER_OT_mute";
-       ot->description = "Mute selected strips";
+       ot->description = "Mute (un)selected strips";
        
        /* api callbacks */
        ot->exec = sequencer_mute_exec;
@@ -1702,7 +1809,7 @@ void SEQUENCER_OT_unmute(struct wmOperatorType *ot)
        /* identifiers */
        ot->name = "Un-Mute Strips";
        ot->idname = "SEQUENCER_OT_unmute";
-       ot->description = "Un-Mute unselected rather than selected strips";
+       ot->description = "Unmute (un)selected strips";
        
        /* api callbacks */
        ot->exec = sequencer_unmute_exec;
@@ -1711,7 +1818,7 @@ void SEQUENCER_OT_unmute(struct wmOperatorType *ot)
        /* flags */
        ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
        
-       RNA_def_boolean(ot->srna, "unselected", 0, "Unselected", "UnMute unselected rather than selected strips");
+       RNA_def_boolean(ot->srna, "unselected", 0, "Unselected", "Unmute unselected rather than selected strips");
 }
 
 
@@ -1828,6 +1935,14 @@ void SEQUENCER_OT_reload(struct wmOperatorType *ot)
 }
 
 /* reload operator */
+static int sequencer_refresh_all_poll(bContext *C)
+{
+       if (G.is_rendering) {
+               return 0;
+       }
+       return sequencer_edit_poll(C);
+}
+
 static int sequencer_refresh_all_exec(bContext *C, wmOperator *UNUSED(op))
 {
        Scene *scene = CTX_data_scene(C);
@@ -1849,7 +1964,7 @@ void SEQUENCER_OT_refresh_all(struct wmOperatorType *ot)
        
        /* api callbacks */
        ot->exec = sequencer_refresh_all_exec;
-       ot->poll = sequencer_edit_poll;
+       ot->poll = sequencer_refresh_all_poll;
 }
 
 static int sequencer_reassign_inputs_exec(bContext *C, wmOperator *op)
@@ -1950,7 +2065,7 @@ void SEQUENCER_OT_swap_inputs(struct wmOperatorType *ot)
 
 
 /* cut operator */
-static EnumPropertyItem prop_cut_types[] = {
+static const EnumPropertyItem prop_cut_types[] = {
        {SEQ_CUT_SOFT, "SOFT", 0, "Soft", ""},
        {SEQ_CUT_HARD, "HARD", 0, "Hard", ""},
        {0, NULL, 0, NULL, NULL}
@@ -2079,7 +2194,7 @@ static int sequencer_add_duplicate_exec(bContext *C, wmOperator *UNUSED(op))
        if (ed == NULL)
                return OPERATOR_CANCELLED;
 
-       BKE_sequence_base_dupli_recursive(scene, NULL, &nseqbase, ed->seqbasep, SEQ_DUPE_CONTEXT);
+       BKE_sequence_base_dupli_recursive(scene, scene, &nseqbase, ed->seqbasep, SEQ_DUPE_CONTEXT, 0);
 
        if (nseqbase.first) {
                Sequence *seq = nseqbase.first;
@@ -2111,7 +2226,7 @@ void SEQUENCER_OT_duplicate(wmOperatorType *ot)
        ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
        
        /* to give to transform */
-       RNA_def_enum(ot->srna, "mode", transform_mode_types, TFM_TRANSLATION, "Mode", "");
+       RNA_def_enum(ot->srna, "mode", rna_enum_transform_mode_types, TFM_TRANSLATION, "Mode", "");
 }
 
 /* delete operator */
@@ -2121,23 +2236,24 @@ static int sequencer_delete_exec(bContext *C, wmOperator *UNUSED(op))
        Editing *ed = BKE_sequencer_editing_get(scene, false);
        Sequence *seq;
        MetaStack *ms;
-       bool nothingSelected = true;
+       bool nothing_selected = true;
 
        seq = BKE_sequencer_active_get(scene);
        if (seq && seq->flag & SELECT) { /* avoid a loop since this is likely to be selected */
-               nothingSelected = false;
+               nothing_selected = false;
        }
        else {
                for (seq = ed->seqbasep->first; seq; seq = seq->next) {
                        if (seq->flag & SELECT) {
-                               nothingSelected = false;
+                               nothing_selected = false;
                                break;
                        }
                }
        }
 
-       if (nothingSelected)
+       if (nothing_selected) {
                return OPERATOR_FINISHED;
+       }
 
        /* for effects and modifiers, try to find a replacement input */
        for (seq = ed->seqbasep->first; seq; seq = seq->next) {
@@ -2279,7 +2395,7 @@ static int sequencer_separate_images_exec(bContext *C, wmOperator *op)
                        /* remove seq so overlap tests don't conflict,
                         * see seq_free_sequence below for the real free'ing */
                        BLI_remlink(ed->seqbasep, seq);
-                       /* if (seq->ipo) seq->ipo->id.us--; */
+                       /* if (seq->ipo) id_us_min(&seq->ipo->id); */
                        /* XXX, remove fcurve and assign to split image strips */
 
                        start_ofs = cfra = BKE_sequence_tx_get_final_left(seq, false);
@@ -2349,7 +2465,7 @@ void SEQUENCER_OT_images_separate(wmOperatorType *ot)
        
        /* api callbacks */
        ot->exec = sequencer_separate_images_exec;
-       ot->invoke = WM_operator_props_popup;
+       ot->invoke = WM_operator_props_popup_confirm;
        ot->poll = sequencer_edit_poll;
        
        /* flags */
@@ -2409,6 +2525,7 @@ static int sequencer_meta_toggle_exec(bContext *C, wmOperator *UNUSED(op))
 #if 1
                BKE_sequence_tx_set_final_left(ms->parseq, ms->disp_range[0]);
                BKE_sequence_tx_set_final_right(ms->parseq, ms->disp_range[1]);
+               BKE_sequence_single_fix(ms->parseq);
                BKE_sequence_calc(scene, ms->parseq);
 #else
                if (BKE_sequence_test_overlap(ed->seqbasep, ms->parseq))
@@ -2611,6 +2728,29 @@ void SEQUENCER_OT_view_all(wmOperatorType *ot)
        ot->flag = OPTYPE_REGISTER;
 }
 
+static int sequencer_view_frame_exec(bContext *C, wmOperator *op)
+{
+       const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
+       ANIM_center_frame(C, smooth_viewtx);
+       
+       return OPERATOR_FINISHED;
+}
+
+void SEQUENCER_OT_view_frame(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "View Frame";
+       ot->idname = "SEQUENCER_OT_view_frame";
+       ot->description = "Reset viewable area to show range around current frame";
+       
+       /* api callbacks */
+       ot->exec = sequencer_view_frame_exec;
+       ot->poll = ED_operator_sequencer_active;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
 /* view_all operator */
 static int sequencer_view_all_preview_exec(bContext *C, wmOperator *UNUSED(op))
 {
@@ -2691,7 +2831,7 @@ static int sequencer_view_zoom_ratio_exec(bContext *C, wmOperator *op)
        float facx = BLI_rcti_size_x(&v2d->mask) / winx;
        float facy = BLI_rcti_size_y(&v2d->mask) / winy;
 
-       BLI_rctf_resize(&v2d->cur, floorf(winx * facx / ratio + 0.5f), floorf(winy * facy / ratio + 0.5f));
+       BLI_rctf_resize(&v2d->cur, ceilf(winx * facx / ratio + 0.5f), ceilf(winy * facy / ratio + 0.5f));
 
        ED_region_tag_redraw(CTX_wm_region(C));
 
@@ -2716,7 +2856,7 @@ void SEQUENCER_OT_view_zoom_ratio(wmOperatorType *ot)
 
 
 #if 0
-static EnumPropertyItem view_type_items[] = {
+static const EnumPropertyItem view_type_items[] = {
        {SEQ_VIEW_SEQUENCE, "SEQUENCER", ICON_SEQ_SEQUENCER, "Sequencer", ""},
        {SEQ_VIEW_PREVIEW,  "PREVIEW", ICON_SEQ_PREVIEW, "Image Preview", ""},
        {SEQ_VIEW_SEQUENCE_PREVIEW,  "SEQUENCER_PREVIEW", ICON_SEQ_SEQUENCER, "Sequencer and Image Preview", ""},
@@ -3074,7 +3214,7 @@ static void seq_copy_del_sound(Scene *scene, Sequence *seq)
                }
        }
        else if (seq->scene_sound) {
-               sound_remove_scene_sound(scene, seq->scene_sound);
+               BKE_sound_remove_scene_sound(scene, seq->scene_sound);
                seq->scene_sound = NULL;
        }
 }
@@ -3093,7 +3233,7 @@ static int sequencer_copy_exec(bContext *C, wmOperator *op)
                return OPERATOR_CANCELLED;
        }
 
-       BKE_sequence_base_dupli_recursive(scene, NULL, &nseqbase, ed->seqbasep, SEQ_DUPE_UNIQUE_NAME);
+       BKE_sequence_base_dupli_recursive(scene, scene, &nseqbase, ed->seqbasep, SEQ_DUPE_UNIQUE_NAME, 0);
 
        /* To make sure the copied strips have unique names between each other add
         * them temporarily to the end of the original seqbase. (bug 25932)
@@ -3160,7 +3300,7 @@ static int sequencer_paste_exec(bContext *C, wmOperator *UNUSED(op))
        ED_sequencer_deselect_all(scene);
        ofs = scene->r.cfra - seqbase_clipboard_frame;
 
-       BKE_sequence_base_dupli_recursive(scene, NULL, &nseqbase, &seqbase_clipboard, SEQ_DUPE_UNIQUE_NAME);
+       BKE_sequence_base_dupli_recursive(scene, scene, &nseqbase, &seqbase_clipboard, SEQ_DUPE_UNIQUE_NAME, 0);
 
        /* transform pasted strips before adding */
        if (ofs) {
@@ -3231,10 +3371,10 @@ static int sequencer_swap_data_exec(bContext *C, wmOperator *op)
        }
 
        if (seq_act->scene_sound)
-               sound_remove_scene_sound(scene, seq_act->scene_sound);
+               BKE_sound_remove_scene_sound(scene, seq_act->scene_sound);
 
        if (seq_other->scene_sound)
-               sound_remove_scene_sound(scene, seq_other->scene_sound);
+               BKE_sound_remove_scene_sound(scene, seq_other->scene_sound);
 
        seq_act->scene_sound = NULL;
        seq_other->scene_sound = NULL;
@@ -3242,8 +3382,11 @@ static int sequencer_swap_data_exec(bContext *C, wmOperator *op)
        BKE_sequence_calc(scene, seq_act);
        BKE_sequence_calc(scene, seq_other);
 
-       if (seq_act->sound) sound_add_scene_sound_defaults(scene, seq_act);
-       if (seq_other->sound) sound_add_scene_sound_defaults(scene, seq_other);
+       if (seq_act->sound) BKE_sound_add_scene_sound_defaults(scene, seq_act);
+       if (seq_other->sound) BKE_sound_add_scene_sound_defaults(scene, seq_other);
+
+       BKE_sequence_invalidate_cache(scene, seq_act);
+       BKE_sequence_invalidate_cache(scene, seq_other);
 
        WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
 
@@ -3311,27 +3454,68 @@ void SEQUENCER_OT_view_ghost_border(wmOperatorType *ot)
        ot->description = "Set the boundaries of the border used for offset-view";
 
        /* api callbacks */
-       ot->invoke = WM_border_select_invoke;
+       ot->invoke = WM_gesture_border_invoke;
        ot->exec = view_ghost_border_exec;
-       ot->modal = WM_border_select_modal;
-       ot->poll = sequencer_view_poll;
-       ot->cancel = WM_border_select_cancel;
+       ot->modal = WM_gesture_border_modal;
+       ot->poll = sequencer_view_preview_poll;
+       ot->cancel = WM_gesture_border_cancel;
 
        /* flags */
        ot->flag = 0;
 
        /* rna */
-       WM_operator_properties_gesture_border(ot, false);
+       WM_operator_properties_gesture_border(ot);
 }
 
 /* rebuild_proxy operator */
-static int sequencer_rebuild_proxy_exec(bContext *C, wmOperator *UNUSED(op))
+
+static int sequencer_rebuild_proxy_invoke(bContext *C, wmOperator *UNUSED(op),
+                                          const wmEvent *UNUSED(event))
 {
        seq_proxy_build_job(C);
 
        return OPERATOR_FINISHED;
 }
 
+static int sequencer_rebuild_proxy_exec(bContext *C, wmOperator *UNUSED(op))
+{
+       Main *bmain = CTX_data_main(C);
+       Scene *scene = CTX_data_scene(C);
+       Editing *ed = BKE_sequencer_editing_get(scene, false);
+       Sequence *seq;
+       GSet *file_list;
+       
+       if (ed == NULL) {
+               return OPERATOR_CANCELLED;
+       }
+
+       file_list = BLI_gset_new(BLI_ghashutil_strhash_p, BLI_ghashutil_strcmp, "file list");
+       
+       SEQP_BEGIN(ed, seq)
+       {
+               if ((seq->flag & SELECT)) {
+                       ListBase queue = {NULL, NULL};
+                       LinkData *link;
+                       short stop = 0, do_update;
+                       float progress;
+
+                       BKE_sequencer_proxy_rebuild_context(bmain, scene, seq, file_list, &queue);
+
+                       for (link = queue.first; link; link = link->next) {
+                               struct SeqIndexBuildContext *context = link->data;
+                               BKE_sequencer_proxy_rebuild(context, &stop, &do_update, &progress);
+                               BKE_sequencer_proxy_rebuild_finish(context, 0);
+                       }
+                       BKE_sequencer_free_imbuf(scene, &ed->seqbase, false);
+               }
+       }
+       SEQ_END
+
+       BLI_gset_free(file_list, MEM_freeN);
+       
+       return OPERATOR_FINISHED;
+}
+
 void SEQUENCER_OT_rebuild_proxy(wmOperatorType *ot)
 {
        /* identifiers */
@@ -3340,16 +3524,101 @@ void SEQUENCER_OT_rebuild_proxy(wmOperatorType *ot)
        ot->description = "Rebuild all selected proxies and timecode indices using the job system";
        
        /* api callbacks */
+       ot->invoke = sequencer_rebuild_proxy_invoke;
        ot->exec = sequencer_rebuild_proxy_exec;
-       ot->poll = ED_operator_sequencer_active;
        
        /* flags */
        ot->flag = OPTYPE_REGISTER;
 }
 
+static int sequencer_enable_proxies_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+       return WM_operator_props_dialog_popup(C, op, 10 * UI_UNIT_X, 5 * UI_UNIT_Y);
+}
+
+static int sequencer_enable_proxies_exec(bContext *C, wmOperator *op)
+{
+       Scene *scene = CTX_data_scene(C);
+       Editing *ed = BKE_sequencer_editing_get(scene, false);
+       Sequence *seq;
+       bool proxy_25 = RNA_boolean_get(op->ptr, "proxy_25");
+       bool proxy_50 = RNA_boolean_get(op->ptr, "proxy_50");
+       bool proxy_75 = RNA_boolean_get(op->ptr, "proxy_75");
+       bool proxy_100 = RNA_boolean_get(op->ptr, "proxy_100");
+       bool overwrite = RNA_boolean_get(op->ptr, "overwrite");
+       bool turnon = true;
+
+       if (ed == NULL || !(proxy_25 || proxy_50 || proxy_75 || proxy_100)) {
+               turnon = false;
+       }
+
+       SEQP_BEGIN(ed, seq)
+       {
+               if ((seq->flag & SELECT)) {
+                       if (ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_IMAGE, SEQ_TYPE_META, SEQ_TYPE_SCENE, SEQ_TYPE_MULTICAM)) {
+                               BKE_sequencer_proxy_set(seq, turnon);
+                               if (seq->strip->proxy == NULL) {
+                                       continue;
+                               }
+
+                               if (proxy_25)
+                                       seq->strip->proxy->build_size_flags |= SEQ_PROXY_IMAGE_SIZE_25;
+                               else 
+                                       seq->strip->proxy->build_size_flags &= ~SEQ_PROXY_IMAGE_SIZE_25;
+                               
+                               if (proxy_50)
+                                       seq->strip->proxy->build_size_flags |= SEQ_PROXY_IMAGE_SIZE_50;
+                               else 
+                                       seq->strip->proxy->build_size_flags &= ~SEQ_PROXY_IMAGE_SIZE_50;
+                               
+                               if (proxy_75)
+                                       seq->strip->proxy->build_size_flags |= SEQ_PROXY_IMAGE_SIZE_75;
+                               else 
+                                       seq->strip->proxy->build_size_flags &= ~SEQ_PROXY_IMAGE_SIZE_75;
+                               
+                               if (proxy_100)
+                                       seq->strip->proxy->build_size_flags |= SEQ_PROXY_IMAGE_SIZE_100;
+                               else 
+                                       seq->strip->proxy->build_size_flags &= ~SEQ_PROXY_IMAGE_SIZE_100;
+                               
+                               if (!overwrite)
+                                       seq->strip->proxy->build_flags |= SEQ_PROXY_SKIP_EXISTING;
+                               else 
+                                       seq->strip->proxy->build_flags &= ~SEQ_PROXY_SKIP_EXISTING;
+                       }
+               }
+       }
+       SEQ_END
+
+       WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
+       
+       return OPERATOR_FINISHED;
+}
+
+void SEQUENCER_OT_enable_proxies(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Set Selected Strip Proxies";
+       ot->idname = "SEQUENCER_OT_enable_proxies";
+       ot->description = "Enable selected proxies on all selected Movie strips";
+       
+       /* api callbacks */
+       ot->invoke = sequencer_enable_proxies_invoke;
+       ot->exec = sequencer_enable_proxies_exec;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER;
+       
+       RNA_def_boolean(ot->srna, "proxy_25", false, "25%", "");
+       RNA_def_boolean(ot->srna, "proxy_50", false, "50%", "");
+       RNA_def_boolean(ot->srna, "proxy_75", false, "75%", "");
+       RNA_def_boolean(ot->srna, "proxy_100", false, "100%", "");
+       RNA_def_boolean(ot->srna, "overwrite", false, "Overwrite", "");
+}
+
 /* change ops */
 
-static EnumPropertyItem prop_change_effect_input_types[] = {
+static const EnumPropertyItem prop_change_effect_input_types[] = {
        {0, "A_B", 0, "A -> B", ""},
        {1, "B_C", 0, "B -> C", ""},
        {2, "A_C", 0, "A -> C", ""},
@@ -3481,12 +3750,21 @@ static int sequencer_change_path_exec(bContext *C, wmOperator *op)
        Editing *ed = BKE_sequencer_editing_get(scene, false);
        Sequence *seq = BKE_sequencer_active_get(scene);
        const bool is_relative_path = RNA_boolean_get(op->ptr, "relative_path");
+       const bool use_placeholders = RNA_boolean_get(op->ptr, "use_placeholders");
+       int minframe, numdigits;
 
        if (seq->type == SEQ_TYPE_IMAGE) {
                char directory[FILE_MAX];
-               const int len = RNA_property_collection_length(op->ptr, RNA_struct_find_property(op->ptr, "files"));
+               int len;
                StripElem *se;
 
+               /* need to find min/max frame for placeholders */
+               if (use_placeholders) {
+                       len = sequencer_image_seq_get_minmax_frame(op, seq->sfra, &minframe, &numdigits);
+               }
+               else {
+                       len = RNA_property_collection_length(op->ptr, RNA_struct_find_property(op->ptr, "files"));
+               }
                if (len == 0)
                        return OPERATOR_CANCELLED;
 
@@ -3504,14 +3782,19 @@ static int sequencer_change_path_exec(bContext *C, wmOperator *op)
                }
                seq->strip->stripdata = se = MEM_callocN(len * sizeof(StripElem), "stripelem");
 
-               RNA_BEGIN (op->ptr, itemptr, "files")
-               {
-                       char *filename = RNA_string_get_alloc(&itemptr, "name", NULL, 0);
-                       BLI_strncpy(se->name, filename, sizeof(se->name));
-                       MEM_freeN(filename);
-                       se++;
+               if (use_placeholders) {
+                       sequencer_image_seq_reserve_frames(op, se, len, minframe, numdigits);
+               }
+               else {
+                       RNA_BEGIN (op->ptr, itemptr, "files")
+                       {
+                               char *filename = RNA_string_get_alloc(&itemptr, "name", NULL, 0);
+                               BLI_strncpy(se->name, filename, sizeof(se->name));
+                               MEM_freeN(filename);
+                               se++;
+                       }
+                       RNA_END;
                }
-               RNA_END;
 
                /* reset these else we wont see all the images */
                seq->anim_startofs = seq->anim_endofs = 0;
@@ -3525,6 +3808,16 @@ static int sequencer_change_path_exec(bContext *C, wmOperator *op)
                /* important else we don't get the imbuf cache flushed */
                BKE_sequencer_free_imbuf(scene, &ed->seqbase, false);
        }
+       else if (ELEM(seq->type, SEQ_TYPE_SOUND_RAM, SEQ_TYPE_SOUND_HD)) {
+               bSound *sound = seq->sound;
+               if (sound == NULL) {
+                       return OPERATOR_CANCELLED;
+               }
+               char filepath[FILE_MAX];
+               RNA_string_get(op->ptr, "filepath", filepath);
+               BLI_strncpy(sound->name, filepath, sizeof(sound->name));
+               BKE_sound_load(bmain, sound);
+       }
        else {
                /* lame, set rna filepath */
                PointerRNA seq_ptr;
@@ -3583,7 +3876,125 @@ void SEQUENCER_OT_change_path(struct wmOperatorType *ot)
        /* flags */
        ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 
-       WM_operator_properties_filesel(ot, FOLDERFILE | IMAGEFILE | MOVIEFILE, FILE_SPECIAL, FILE_OPENFILE,
-                                      WM_FILESEL_DIRECTORY | WM_FILESEL_RELPATH | WM_FILESEL_FILEPATH | WM_FILESEL_FILES,
-                                      FILE_DEFAULTDISPLAY);
+       WM_operator_properties_filesel(
+               ot, FILE_TYPE_FOLDER, FILE_SPECIAL, FILE_OPENFILE,
+               WM_FILESEL_DIRECTORY | WM_FILESEL_RELPATH | WM_FILESEL_FILEPATH | WM_FILESEL_FILES,
+               FILE_DEFAULTDISPLAY, FILE_SORT_ALPHA);
+       RNA_def_boolean(ot->srna, "use_placeholders", false, "Use Placeholders", "Use placeholders for missing frames of the strip");
+}
+
+static int sequencer_export_subtitles_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+       if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
+               char filepath[FILE_MAX];
+
+               if (G.main->name[0] == 0)
+                       BLI_strncpy(filepath, "untitled", sizeof(filepath));
+               else
+                       BLI_strncpy(filepath, G.main->name, sizeof(filepath));
+
+               BLI_replace_extension(filepath, sizeof(filepath), ".srt");
+               RNA_string_set(op->ptr, "filepath", filepath);
+       }
+
+       WM_event_add_fileselect(C, op);
+
+       return OPERATOR_RUNNING_MODAL;
+}
+
+static int sequencer_export_subtitles_exec(bContext *C, wmOperator *op)
+{
+       Scene *scene = CTX_data_scene(C);
+       Sequence *seq, *seq_next;
+       Editing *ed = BKE_sequencer_editing_get(scene, false);
+       ListBase text_seq = {0};
+       int iter = 0;
+       FILE *file;
+       char filepath[FILE_MAX];
+
+       if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
+               BKE_report(op->reports, RPT_ERROR, "No filename given");
+               return OPERATOR_CANCELLED;
+       }
+
+       RNA_string_get(op->ptr, "filepath", filepath);
+       BLI_ensure_extension(filepath, sizeof(filepath), ".srt");
+
+       /* Avoid File write exceptions */
+       if (!BLI_exists(filepath)) {
+               BLI_make_existing_file(filepath);
+               if (!BLI_file_touch(filepath)) {
+                       BKE_report(op->reports, RPT_ERROR, "Can't create subtitle file");
+                       return OPERATOR_CANCELLED;
+               }
+       }
+       else if (!BLI_file_is_writable(filepath)) {
+               BKE_report(op->reports, RPT_ERROR, "Can't overwrite export file");
+               return OPERATOR_CANCELLED;
+       }
+
+       SEQ_BEGIN(ed, seq)
+       {
+               if (seq->type == SEQ_TYPE_TEXT) {
+                       BLI_addtail(&text_seq, MEM_dupallocN(seq));
+               }
+       }
+       SEQ_END
+
+       if (BLI_listbase_is_empty(&text_seq)) {
+               BKE_report(op->reports, RPT_ERROR, "No subtitles (text strips) to export");
+               return OPERATOR_CANCELLED;
+       }
+
+       BLI_listbase_sort(&text_seq, BKE_sequencer_cmp_time_startdisp);
+
+       /* time to open and write! */
+       file = BLI_fopen(filepath, "w");
+
+       for (seq = text_seq.first; seq; seq = seq_next) {
+               TextVars *data = seq->effectdata;
+               char timecode_str_start[32];
+               char timecode_str_end[32];
+
+               BLI_timecode_string_from_time(timecode_str_start, sizeof(timecode_str_start),
+                                             -2, FRA2TIME(seq->startdisp), FPS, USER_TIMECODE_SUBRIP);
+               BLI_timecode_string_from_time(timecode_str_end, sizeof(timecode_str_end),
+                                             -2, FRA2TIME(seq->enddisp), FPS, USER_TIMECODE_SUBRIP);
+
+               fprintf(file, "%d\n%s --> %s\n%s\n\n", iter++, timecode_str_start, timecode_str_end, data->text);
+
+               seq_next = seq->next;
+               MEM_freeN(seq);
+       }
+
+       fclose(file);
+
+       return OPERATOR_FINISHED;
+}
+
+static int sequencer_strip_is_text_poll(bContext *C)
+{
+       Editing *ed;
+       Sequence *seq;
+       return (((ed = BKE_sequencer_editing_get(CTX_data_scene(C), false)) != NULL) && ((seq = ed->act_seq) != NULL) && (seq->type == SEQ_TYPE_TEXT));
+}
+
+void SEQUENCER_OT_export_subtitles(struct wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Export Subtitles";
+       ot->idname = "SEQUENCER_OT_export_subtitles";
+       ot->description = "Export .srt file containing text strips";
+
+       /* api callbacks */
+       ot->exec = sequencer_export_subtitles_exec;
+       ot->invoke = sequencer_export_subtitles_invoke;
+       ot->poll = sequencer_strip_is_text_poll;
+
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+       WM_operator_properties_filesel(
+               ot,  FILE_TYPE_FOLDER, FILE_BLENDER, FILE_SAVE,
+               WM_FILESEL_FILEPATH, FILE_DEFAULTDISPLAY, FILE_SORT_ALPHA);
 }