def save_out(tracks, outfile=None, filetype='mp4'): out = [] vids = [t for t in tracks if t['type'] == 'vid'] texts = [t for t in tracks if t['type'] == 'text'] for v in vids: c = VideoFileClip(v['content']).subclip(v['in'], v['in'] + v['duration']) c = c.set_start(v['start']) out.append(c) size = out[0].size for t in texts: c = create_sub(t['content'], size, rect_offset=195, min_height=55) c = c.set_start(t['start']) c = c.set_duration(t['duration']) out.append(c) final_clip = CompositeVideoClip(out) if outfile is None: outfile = 'msg_' + str(int(time.time())) + '.mp4' if filetype == 'gif': outfile = outfile.replace('.mp4', '.gif') final_clip.speedx(1.7).write_gif(outfile, fps=7, loop=1) else: final_clip.write_videofile(outfile, fps=24, codec='libx264') return outfile
class Clip: _cache: dict[Path, VideoFileClip] = {} def __init__(self, meta: ClipMeta): self.meta = meta if meta.path not in Clip._cache: Clip._cache[meta.path] = VideoFileClip(str(meta.path)) self.video_file_clip = Clip._cache[meta.path] def cut(self): if self.meta.start is not None and self.meta.end is not None: logger.info( '%s: Cutting %s -> %s', self.meta.path, self.meta.start, self.meta.end, ) self.video_file_clip = self.video_file_clip.subclip( self.meta.start.total_seconds(), self.meta.end.total_seconds()) def set_fps(self): self.video_file_clip = self.video_file_clip.set_fps(self.video_fps) def resize(self, width: int, height: int): current_width = self.video_file_clip.w current_height = self.video_file_clip.h current_aspect_ratio = current_width / current_height new_aspect_ratio = width / height if current_width == width and current_height == height: logger.info('%s: Resizing not necessary', self.meta.path) return crop_x: float = 0 crop_y: float = 0 if new_aspect_ratio > current_aspect_ratio: new_width: float = width new_height: float = round(new_width / current_aspect_ratio) crop_y = (new_height - height) / 2 elif new_aspect_ratio < current_aspect_ratio: new_height = height new_width = round(new_height * current_aspect_ratio) crop_x = (new_width - width) / 2 else: new_width = width new_height = height logger.info( '%s: Resizing from %f x %f [%f] to %f x %f [%f] ', self.meta.path, current_width, current_height, current_aspect_ratio, new_width, new_height, new_aspect_ratio, ) self.video_file_clip = self.video_file_clip.resize( (new_width, new_height)) if crop_x > 0 or crop_y > 0: logger.info( '%s: Cropping +%f+%f', self.meta.path, crop_x, crop_y, ) self.video_file_clip = self.video_file_clip.crop(x1=crop_x, y1=crop_y, width=width, height=height) def add_subtitles( self, subtitles_path: Path, color: str = DEFAULT_SUBTITLE_COLOR, font: str = DEFAULT_SUBTITLE_FONT, fontsize: int = DEFAULT_SUBTITLE_FONTSIZE, ): """Currently unused""" def subtitle_text_clip_factory(text: str) -> TextClip: return TextClip(text, font, fontsize, color) subtitles_clip = SubtitlesClip(subtitles_path, subtitle_text_clip_factory) self.video_file_clip = CompositeVideoClip( [self.video_file_clip, subtitles_clip]) def prepend_intertitle( self, size: Optional[Size] = None, color: str = DEFAULT_INTERTITLE_COLOR, font: str = DEFAULT_INTERTITLE_FONT, fontsize: int = DEFAULT_INTERTITLE_FONTSIZE, position: str = DEFAULT_INTERTITLE_POSITION, duration: int = DEFAULT_INTERTITLE_DURATION, ): if not self.meta.text: logger.warning('%s: Missing intertitle text') return logger.info('%s: Intertitle "%s"', self.meta.path, self.meta.text) if not size: size = Size(width=self.video_file_clip.w, height=self.video_file_clip.h) text_clip = TextClip( self.meta.text.replace('|', '\n'), size=(size.width * INTERTITLE_TEXT_WIDTH_FACTOR, None), color=color, font=font, fontsize=fontsize, method='caption', align='center', ) composite_clip = CompositeVideoClip([text_clip.set_pos(position)], (size.width, size.height)) intertitle_clip = composite_clip.subclip(0, duration) self.video_file_clip = concatenate_videoclips( [intertitle_clip, self.video_file_clip], method='compose') def fadeout(self, duration: float): self.video_file_clip = self.video_file_clip.fadeout(duration / 1000) def speed(self, factor: float): self.video_file_clip = self.video_file_clip.speedx(factor=factor)