Skip to content

Instantly share code, notes, and snippets.

@aksinghdce
Last active April 24, 2023 17:40
Show Gist options
  • Save aksinghdce/44d0b53d905ca7506fa34264b2418107 to your computer and use it in GitHub Desktop.
Save aksinghdce/44d0b53d905ca7506fa34264b2418107 to your computer and use it in GitHub Desktop.
In blender, with python, add multiple video files in Video Sequence Editor

Video input from multiple camera sources super-imposed on each other

  1. Take videos from back-facing-camera and put it in channel 2. Add audio but keep it mute.
  2. Take videos from front-facing-camera in to channel 1 of blender's sequence editor. Make sure that the two channels have videos of same time duration.
import bpy
from pathlib import Path
from datetime import datetime
import math

class BlenderVideoEditor:
    def __init__(self):
        '''initialize start_frame and duration of video-synced-audio'''
        self.vsa="/media/amit/4CFC-8D04/DCIM/1"
        self.start_n_dur=list()
        self.mask_images = list()
    def get_video_file_names(self, dir="/media/amit/4CFC-8D04/DCIM/1"):
        p = Path(dir)
        return [x.absolute() for x in sorted(p.iterdir()) if not x.is_dir()]
    def add_movie_clips_from(self, dir="/media/amit/4CFC-8D04/DCIM/2", start_frame=1, channel=2):
        frame_s = start_frame
        for i, x in enumerate(self.get_video_file_names(dir=dir)):
            clip_v = bpy.context.scene.sequence_editor.sequences.new_movie(name=str(i),filepath=str(x),channel=channel, frame_start = frame_s)
            clip_a = bpy.context.scene.sequence_editor.sequences.new_sound(name=str(i),filepath=str(x),channel=channel+1, frame_start = frame_s)
            dur = max([clip_v.frame_duration, clip_a.frame_duration])
            self.start_n_dur.append((frame_s, dur))
            frame_s = frame_s + dur
    def add_dependent_movie_clip(self, dir="/media/amit/4CFC-8D04/DCIM/1", start_frame=1, channel=1):
        assert len(self.start_n_dur) > 0
        try:
            for i, x in enumerate(self.get_video_file_names(dir=dir)):
                clip_v = bpy.context.scene.sequence_editor.sequences.new_movie(name=str(i), filepath=str(x), channel=channel, frame_start=self.start_n_dur[i][0])
                clip_v.frame_final_duration = self.start_n_dur[i][1]
        except Exception as e:
            print(e)
    def transform_overlay_video(self, channel=3):
        '''Ensure that the overlay video is positioned at near top right corner'''
        for strip in bpy.data.scenes["Scene"].sequence_editor.sequences_all:
            if strip.channel == channel:
                assert strip.type == "MOVIE"
                strip.transform.offset_x = 440
                strip.transform.offset_y = 260
                strip.transform.scale_x = 0.8
                strip.transform.scale_y = 0.8
    def mute_sequence(movieSequence=None, mute=True):
        '''Mute a supplied movie sequence'''
        if None != movieSequence:
            movieSequence.mute = mute
    def create_mask(self, file="", for_moviesequence=None):
        # name for a new image that would be used as a mask
        time_string = datetime.now().strftime("%f")
        mask_image = None
        if len(self.mask_images) == 0:
            mask_image = bpy.data.scenes["Scene"].sequence_editor.sequences.new_image(name=time_string, filepath=file, channel=4, frame_start=1)
            self.mask_images.append(mask_image)
            if None != for_moviesequence:
                mask_image.frame_final_duration = for_moviesequence.frame_duration
        else:
            mask_image = self.mask_images[-1]
        for_moviesequence.modifiers.new(name="circle", type="MASK")
        if None != mask_image:
            for_moviesequence.modifiers["circle"].input_mask_strip = mask_image
    def rotate_moviesequences_by_180(self):
        for seq in bpy.data.scenes["Scene"].sequence_editor.sequences:
            if 'MOVIE' == seq.type:
                seq.transform.rotation = math.pi
    def render(self):
        bpy.context.scene.render.resolution_percentage = 100
        bpy.context.scene.render.use_file_extension = True
        bpy.context.scene.render.image_settings.file_format = 'FFMPEG'
        bpy.context.scene.render.ffmpeg.format = 'MPEG4'
        bpy.context.scene.render.fps = 24
        bpy.context.scene.render.use_overwrite = True
        bpy.context.scene.render.filepath = '/home/amit/Downloads/final_timelapse.mp4'
        
        bpy.context.scene.frame_start = 1
        bpy.context.scene.frame_end = 239326
        step = 14
        for frame in range(bpy.context.scene.frame_start, bpy.context.scene.frame_end + 1, step):
            bpy.context.scene.frame_set(frame)
            bpy.ops.render.render(write_still=False)
        
    def process(self):
        self.add_movie_clips_from()
        self.add_dependent_movie_clip()
        self.transform_overlay_video(channel=2)
        mask_image_file = "/home/amit/Documents/pendrive/Career-Template/7-podcasts-blender/3-dashcam-template/masks/0.png"
        for seq in bpy.data.scenes["Scene"].sequence_editor.sequences:
            if 'MOVIE' == seq.type and 2 == seq.channel:
                self.create_mask(file=mask_image_file, for_moviesequence=seq)
        for seq in bpy.data.scenes["Scene"].sequence_editor.sequences:
            if 3==seq.channel:
                seq.mute = True
        #self.rotate_moviesequences_by_180()
        #self.render()
        
if __name__ == '__main__':
    bve = BlenderVideoEditor()
    bve.process()

This one is for adding english text based subtitles anywhere in the timeline of video sequence editor:

import bpy
from pathlib import Path
from datetime import datetime
import math

class BlenderVideoEditor:
    def __init__(self):
        '''initialize start_frame and duration of video-synced-audio'''
        self.vsa="/media/amit/4CFC-8D04/DCIM/1"
        self.start_n_dur=list()
        self.mask_images = list()
    def get_video_file_names(self, dir="/media/amit/4CFC-8D04/DCIM/1"):
        p = Path(dir)
        return [x.absolute() for x in sorted(p.iterdir()) if not x.is_dir()]
    def add_movie_clips_from(self, dir="/media/amit/4CFC-8D04/DCIM/2", start_frame=1, channel=2):
        frame_s = start_frame
        for i, x in enumerate(self.get_video_file_names(dir=dir)):
            clip_v = bpy.context.scene.sequence_editor.sequences.new_movie(name=str(i),filepath=str(x),channel=channel, frame_start = frame_s)
            clip_a = bpy.context.scene.sequence_editor.sequences.new_sound(name=str(i),filepath=str(x),channel=channel+1, frame_start = frame_s)
            dur = max([clip_v.frame_duration, clip_a.frame_duration])
            self.start_n_dur.append((frame_s, dur))
            frame_s = frame_s + dur
    def add_dependent_movie_clip(self, dir="/media/amit/4CFC-8D04/DCIM/1", start_frame=1, channel=1):
        assert len(self.start_n_dur) > 0
        try:
            for i, x in enumerate(self.get_video_file_names(dir=dir)):
                clip_v = bpy.context.scene.sequence_editor.sequences.new_movie(name=str(i), filepath=str(x), channel=channel, frame_start=self.start_n_dur[i][0])
                clip_v.frame_final_duration = self.start_n_dur[i][1]
        except Exception as e:
            print(e)
    def transform_overlay_video(self, channel=3):
        '''Ensure that the overlay video is positioned at near top right corner'''
        for strip in bpy.data.scenes["Scene"].sequence_editor.sequences_all:
            if strip.channel == channel:
                assert strip.type == "MOVIE"
                strip.transform.offset_x = 440
                strip.transform.offset_y = 260
                strip.transform.scale_x = 0.8
                strip.transform.scale_y = 0.8
    def mute_sequence(movieSequence=None, mute=True):
        '''Mute a supplied movie sequence'''
        if None != movieSequence:
            movieSequence.mute = mute
    def create_mask(self, file="", for_moviesequence=None):
        # name for a new image that would be used as a mask
        time_string = datetime.now().strftime("%f")
        mask_image = None
        if len(self.mask_images) == 0:
            mask_image = bpy.data.scenes["Scene"].sequence_editor.sequences.new_image(name=time_string, filepath=file, channel=4, frame_start=1)
            self.mask_images.append(mask_image)
            if None != for_moviesequence:
                mask_image.frame_final_duration = for_moviesequence.frame_duration
        else:
            mask_image = self.mask_images[-1]
        for_moviesequence.modifiers.new(name="circle", type="MASK")
        if None != mask_image:
            for_moviesequence.modifiers["circle"].input_mask_strip = mask_image
    def rotate_moviesequences_by_180(self):
        for seq in bpy.data.scenes["Scene"].sequence_editor.sequences:
            if 'MOVIE' == seq.type:
                seq.transform.rotation = math.pi
    def render(self):
        bpy.context.scene.render.resolution_percentage = 100
        bpy.context.scene.render.use_file_extension = True
        bpy.context.scene.render.image_settings.file_format = 'FFMPEG'
        bpy.context.scene.render.ffmpeg.format = 'MPEG4'
        bpy.context.scene.render.fps = 24
        bpy.context.scene.render.use_overwrite = True
        bpy.context.scene.render.filepath = '/home/amit/Downloads/final_timelapse.mp4'
        
        bpy.context.scene.frame_start = 1
        bpy.context.scene.frame_end = 239326
        step = 14
        for frame in range(bpy.context.scene.frame_start, bpy.context.scene.frame_end + 1, step):
            bpy.context.scene.frame_set(frame)
            bpy.ops.render.render(write_still=False)
    def add_mask_to_all_in_channel(self, channel=2, mask_file="/home/amit/Documents/pendrive/Career-Template/7-podcasts-blender/3-dashcam-template/masks/0.png"):
        mask_image_file = mask_file
        for seq in bpy.data.scenes["Scene"].sequence_editor.sequences:
            if 'MOVIE' == seq.type and channel == seq.channel:
                self.create_mask(file=mask_image_file, for_moviesequence=seq)
    def mute_all_in_channel(self, channel=3):
        for seq in bpy.data.scenes["Scene"].sequence_editor.sequences:
            if channel==seq.channel:
                seq.mute = True
    def add_text_strip(self):
        '''Add text strip to the video sequence'''
        path="///home/amit/Documents/pendrive/Career-Template/7-podcasts-blender/2-personal-youtube-channel/Diplomatic-Dispatch-Series-Sansad-Tv/LabelTrack.txt"
        p = Path(path)
        lines = list()
        with open(p, "r",  encoding='utf-8-sig') as f:
            lines = f.readlines()
        ts = None
        for l in lines:
            la = l.split("\t")
            fs = (int(float(la[0].strip())) + 1) * 15
            fe = ((int(float(la[1].strip())) + 1) * 15 ) + 3
            ts = context.scene.sequence_editor.sequences.new_effect(name="subtitle", type="TEXT", channel=6, frame_start = fs, frame_end = fe)
            ts = context.scene.sequence_editor.sequences.new_effect(name="subtitle", type="TEXT", channel=6, frame_start = fs, frame_end = fe)
            ts = context.scene.sequence_editor.sequences.new_effect(name="subtitle", type="TEXT", channel=6, frame_start = fs, frame_end = fe)
            ts.text = l.split("\t")[2].strip()
            ts.transform.offset_y=-444
            ts.font_size = 42
    def add_banner_at(self, font_size=42 ,start_f=1, x=0, y=0, w=1, h=0.1, text="", dur=64826, channel=5, banner_color=(0, 0.5, 0.5), text_color=(1, 1, 1, 1)):
        '''Add a banner with text'''
        frame_s = start_f
        timestring = datetime.now().strftime("%f")
        color_clip = bpy.context.scene.sequence_editor.sequences.new_effect(name="color"+timestring, type="COLOR", channel = channel+2, frame_start = frame_s, frame_end = frame_s + 240)
        color_clip.color = banner_color
        color_clip.blend_type = 'ALPHA_OVER'
        color_clip.blend_alpha = 0.5
        color_clip.transform.scale_y = h
        color_clip.transform.scale_x = w
        color_clip.transform.offset_y = y
        color_clip.transform.offset_x = x
        subtitle_clip = bpy.context.scene.sequence_editor.sequences.new_effect(name="subtitle"+timestring, type="TEXT", channel = channel+3, frame_start = frame_s, frame_end = frame_s + 240)
#                subtitle_clip.text = str(x[1]) + " UTC; " + str(self.readExif(filename=str(x[0]))) 
        subtitle_clip.text = text
        subtitle_clip.color = text_color
        subtitle_clip.transform.offset_y= y
        subtitle_clip.transform.offset_x= x
        subtitle_clip.font_size = font_size
        color_clip.frame_final_duration = dur
        subtitle_clip.frame_final_duration = dur
    def add_image(self, file=""):
        '''add image in the sequence editor'''
        timestring = datetime.now().strftime("%f")
        img = bpy.context.scene.sequence_editor.sequences.new_image(name=timestring, filepath=file, channel=16, frame_start=3646)
        img.frame_final_duration = 1827
        
    def process(self):
        #self.add_movie_clips_from(dir="/home/amit/Documents/pendrive/Career-Template/7-podcasts-blender/0-workbench/2-Site-Reliability-Engineering-In-IT-Consulting/media/videos")
        #self.add_banner_at(text="CICD+'Repository Management' in one Application", y=-180, font_size=24, dur=1280, channel=10, text_color=(1, 0.12, 0.33, 1))
#        self.add_banner_at(text="CICD+'Repository Management' in one Application", y=-500, font_size=42, dur=1280, channel=10, text_color=(1, 0.12, 0.33, 1))
#        self.add_banner_at(text="Supports DevSecOps philosophy by design", start_f=2128 , y=-500, font_size=42, dur=3345, channel=10, text_color=(1, 0.12, 0.33, 1))
#        self.add_banner_at(x=-500,y=130,w=0.4,h=0.2,text="Traditional Agile", start_f=2128 , font_size=42, dur=3345, channel=11, text_color=(1, 0.12, 0.33, 1))        
        self.add_image(file="/home/amit/Documents/pendrive/Career-Template/7-podcasts-blender/0-workbench/2-Site-Reliability-Engineering-In-IT-Consulting/media/images/SRE-DevOps.png")
if __name__ == '__main__':
    bve = BlenderVideoEditor()
    bve.process()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment