Skip to content

Instantly share code, notes, and snippets.

@aksinghdce
Created April 25, 2023 21:52
Show Gist options
  • Save aksinghdce/ce8b648f2bc078e418d7f17f727eb753 to your computer and use it in GitHub Desktop.
Save aksinghdce/ce8b648f2bc078e418d7f17f727eb753 to your computer and use it in GitHub Desktop.
Code to generate video with subtitles using Audacity, Python and Blender
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, file="///home/amit/Documents/pendrive/Career-Template/7-podcasts-blender/2-personal-youtube-channel/Diplomatic-Dispatch-Series-Sansad-Tv/LabelTrack.txt", fps=30):
        '''Add text strip to the video sequence'''
        path=file
        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) * fps
            fe = ((int(float(la[1].strip())) + 1) * fps ) + 3
            self.add_banner_at(y=-375, w=1.5, h=0.2, start_f=fs, dur=fe-fs, text=l.split("\t")[2].strip())
            #ts = bpy.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="/media/amit/AKS-MEL-DSS")
        #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="Master of Engineering Leadership at UBC, Vancouver in 2018", start_f=1 , y=-500, font_size=42, dur=68093, channel=5, text_color=(1, 1, 0, 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")
        self.add_text_strip(file="/media/amit/AKS-MEL-DSS/LabelTrack.txt")
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