示例#1
0
 def handleDLToMp4(self):
     try:
         appStatus.set('[1/2] Downloading...')
         root.update()
         getYTVideo = YouTube(ytLink.get())
         composedFilePath = f'{self.usrDownloadPath}{sep}{getYTVideo.title}.mp4'
         getYTVideo.streams.filter(adaptive=True,
                                   type='video').first().download(
                                       self.usrDownloadPath,
                                       filename='tmpVidFile')
         getYTVideo.streams.filter(adaptive=True,
                                   type='audio').first().download(
                                       self.usrDownloadPath,
                                       filename='tmpAudFile')
         tmpVideoFile = VideoFileClip(self.tmpVideoFilePath)
         tmpAudioFile = AudioFileClip(self.tmpAudioFilePath)
         appStatus.set('[2/2] Converting & mounting file...')
         ytLink.set('This step may take some minutes')
         root.update()
         mountClip = tmpVideoFile.set_audio(tmpAudioFile)
         mountClip.write_videofile(composedFilePath, fps=30)
         tmpVideoFile.close()
         tmpAudioFile.close()
         remove(self.tmpVideoFilePath)
         remove(self.tmpAudioFilePath)
         appStatus.set('Done!')
         ytLink.set('Check your "Downloads" directory.')
         root.update()
     except Exception as e:
         print(e)
         appStatus.set('Whoops, something went wrong!')
         ytLink.set(value='Invalid link!')
         root.update()
示例#2
0
    def get_audio(self):
        created = False
        audio = Audio()

        self._audio_file = f'{self.filename}.mp3'

        clip = AudioFileClip(self._video_file)
        clip.write_audiofile(self._audio_file)
        clip.close()

        try:
            audio.mp3 = File(open(self._audio_file, mode='rb'))

            print('File was successfully uploaded.')
            audio.name = self._title
            audio.api = 'PyTube API'

            try:
                audio.save()
            except:
                print('Object was not saved!')

            created = True

        except Exception:
            print('File not found!')
            self._highlight_title()

        if created:
            return created, audio

        else:
            return created, None
示例#3
0
def get_mp3(url):
    youtube_video = YouTube(url)

    title = special_characters(youtube_video.title)

    # Converting MP4 to MP3
    mp3_file = f'{title}.mp3'
    mp4_file = f'{title}.mp4'

    youtube_video.streams.filter(only_audio=True).first().download()

    mp3_file_path = os.path.join(BASE_DIR, mp3_file)
    mp4_file_path = os.path.join(BASE_DIR, mp4_file)

    clip = AudioFileClip(mp4_file_path)
    clip.write_audiofile(mp3_file_path)
    clip.close()

    # os.remove(mp4_file_path)

    audio = Audio()
    audio.mp3 = File(open(mp3_file, mode='rb'))
    # audio.image = img
    audio.image = youtube_video.thumbnail_url
    audio.name = youtube_video.title
    audio.save()

    if mp4_file in os.listdir(BASE_DIR):
        os.remove(mp4_file)

    if mp3_file in os.listdir(BASE_DIR):
        os.remove(mp3_file)
示例#4
0
    def download_Audio(self):
        yt_link = self.linkAudio.get()
        try:
            if yt_link == '' or yt_link == None:
                messagebox.showerror(f"Aviso", "Campo Vazio")
            else:
                path_audio = 'C:/Temp/Musicas'

                yt = YouTube(yt_link)
                audio = yt.streams.filter(only_audio=True)[0]

                mp4_file = audio.download(path_audio)
                mp3_file = audio.default_filename[:-4] + ".mp3"

                videoClip = AudioFileClip(mp4_file, fps=44100)
                audioclip = videoClip
                audioclip.write_audiofile(path_audio + '/' + mp3_file,
                                          fps=44100)

                os.remove(path_audio + '/' + audio.default_filename)

                videoClip.close()
                audioclip.close()

                messagebox.showinfo(f"Aviso", "Download Completo!")
                self.linkAudio.delete(0, END)
        except:
            messagebox.showerror(f"Erro", "Arquivo invalido para Download!")
示例#5
0
    def convert(self):
        audio = askopenfilenames()

        try:
            for num in audio:
                mp4_file = num
                mp3_file = mp4_file[:-4] + ".mp3"

                musica = AudioFileClip(mp4_file, fps=44100)
                audioClip = musica
                audioClip.write_audiofile(mp3_file, fps=44100, logger="bar")

                if num[:-4] + ".mp4" == num:
                    os.remove(num)

                musica.close()
                audioClip.close()
                messagebox.showinfo(f"Aviso",
                                    "Convertido para MP3 com sucesso!")
        except:

            messagebox.showerror(f"Erro", "Arquivo invalido para converter!")
示例#6
0
    def download_audio(self, vid, qual, target_dir, ind, n):
        if qual == "Highest":
            path = vid.streams.get_audio_only(
                subtype="webm").download(target_dir)

        for stream in vid.streams:
            if stream.mime_type == "audio/webm" and stream.abr == qual:
                path = stream.download(target_dir)
                break
        else:
            path = vid.streams.get_audio_only(
                subtype="webm").download(target_dir)

        new_path = path[:-5] + ".mp3"
        file = AudioFileClip(path)
        self.message_label.setText(
            f"Converting audio stream to mp3.. ({ind + 1} of {n})")
        file.write_audiofile(new_path)
        file.close()
        os.remove(path)

        metadata = vid.metadata.metadata
        f = music_tag.load_file(new_path)
        f['artist'] = (metadata[0]['Artist'] if len(metadata) > 0
                       and metadata[0].get('Artist') is not None else
                       vid.author)
        f['comment'] = vid.description
        f['compilation'] = False
        f['composer'] = f['artist']
        f['tracktitle'] = (metadata[0]['Song'] if len(metadata) > 0
                           and metadata[0].get('Song') is not None else
                           vid.title)
        f['year'] = vid.publish_date.year
        f.save()

        return new_path
示例#7
0
class VideoFileClip(VideoClip):
    """
    A video clip originating from a movie file. For instance: ::

        >>> clip = VideoFileClip("myHolidays.mp4")
        >>> clip.close()
        >>> with VideoFileClip("myMaskVideo.avi") as clip2:
        >>>    pass  # Implicit close called by context manager.


    Parameters
    ----------

    filename:
      The name of the video file, as a string or a path-like object.
      It can have any extension supported by ffmpeg:
      .ogv, .mp4, .mpeg, .avi, .mov etc.

    has_mask:
      Set this to 'True' if there is a mask included in the videofile.
      Video files rarely contain masks, but some video codecs enable
      that. For instance if you have a MoviePy VideoClip with a mask you
      can save it to a videofile with a mask. (see also
      ``VideoClip.write_videofile`` for more details).

    audio:
      Set to `False` if the clip doesn't have any audio or if you do not
      wish to read the audio.

    target_resolution:
      Set to (desired_width, desired_height) to have ffmpeg resize the frames
      before returning them. This is much faster than streaming in high-res
      and then resizing. If either dimension is None, the frames are resized
      by keeping the existing aspect ratio.

    resize_algorithm:
      The algorithm used for resizing. Default: "bicubic", other popular
      options include "bilinear" and "fast_bilinear". For more information, see
      https://ffmpeg.org/ffmpeg-scaler.html

    fps_source:
      The fps value to collect from the metadata. Set by default to 'fps', but
      can be set to 'tbr', which may be helpful if you are finding that it is reading
      the incorrect fps from the file.

    pixel_format
      Optional: Pixel format for the video to read. If is not specified
      'rgb24' will be used as the default format unless ``has_mask`` is set
      as ``True``, then 'rgba' will be used.


    Attributes
    ----------

    filename:
      Name of the original video file.

    fps:
      Frames per second in the original file.


    Read docs for Clip() and VideoClip() for other, more generic, attributes.

    Lifetime
    --------

    Note that this creates subprocesses and locks files. If you construct one
    of these instances, you must call close() afterwards, or the subresources
    will not be cleaned up until the process ends.

    If copies are made, and close() is called on one, it may cause methods on
    the other copies to fail.

    """
    @convert_path_to_string("filename")
    def __init__(
        self,
        filename,
        decode_file=False,
        has_mask=False,
        audio=True,
        audio_buffersize=200000,
        target_resolution=None,
        resize_algorithm="bicubic",
        audio_fps=44100,
        audio_nbytes=2,
        fps_source="fps",
        pixel_format=None,
    ):

        VideoClip.__init__(self)

        # Make a reader
        if not pixel_format:
            pixel_format = "rgba" if has_mask else "rgb24"
        self.reader = FFMPEG_VideoReader(
            filename,
            decode_file=decode_file,
            pixel_format=pixel_format,
            target_resolution=target_resolution,
            resize_algo=resize_algorithm,
            fps_source=fps_source,
        )

        # Make some of the reader's attributes accessible from the clip
        self.duration = self.reader.duration
        self.end = self.reader.duration

        self.fps = self.reader.fps
        self.size = self.reader.size
        self.rotation = self.reader.rotation

        self.filename = filename

        if has_mask:

            self.make_frame = lambda t: self.reader.get_frame(t)[:, :, :3]

            def mask_make_frame(t):
                return self.reader.get_frame(t)[:, :, 3] / 255.0

            self.mask = VideoClip(is_mask=True,
                                  make_frame=mask_make_frame).with_duration(
                                      self.duration)
            self.mask.fps = self.fps

        else:

            self.make_frame = lambda t: self.reader.get_frame(t)

        # Make a reader for the audio, if any.
        if audio and self.reader.infos["audio_found"]:

            self.audio = AudioFileClip(
                filename,
                buffersize=audio_buffersize,
                fps=audio_fps,
                nbytes=audio_nbytes,
            )

    def __deepcopy__(self, memo):
        """Implements ``copy.deepcopy(clip)`` behaviour as ``copy.copy(clip)``.

        VideoFileClip class instances can't be deeply copied because the locked Thread
        of ``proc`` isn't pickleable. Without this override, calls to
        ``copy.deepcopy(clip)`` would raise a ``TypeError``:

        ```
        TypeError: cannot pickle '_thread.lock' object
        ```
        """
        return self.__copy__()

    def close(self):
        """Close the internal reader."""
        if self.reader:
            self.reader.close()
            self.reader = None

        try:
            if self.audio:
                self.audio.close()
                self.audio = None
        except AttributeError:  # pragma: no cover
            pass
示例#8
0
class VideoFileClip(VideoClip):

    """

    A video clip originating from a movie file. For instance: ::

        >>> clip = VideoFileClip("myHolidays.mp4")
        >>> clip.close()
        >>> with VideoFileClip("myMaskVideo.avi") as clip2:
        >>>    pass  # Implicit close called by contex manager.


    Parameters
    ------------

    filename:
      The name of the video file. It can have any extension supported
      by ffmpeg: .ogv, .mp4, .mpeg, .avi, .mov etc.

    has_mask:
      Set this to 'True' if there is a mask included in the videofile.
      Video files rarely contain masks, but some video codecs enable
      that. For istance if you have a MoviePy VideoClip with a mask you
      can save it to a videofile with a mask. (see also
      ``VideoClip.write_videofile`` for more details).

    audio:
      Set to `False` if the clip doesn't have any audio or if you do not
      wish to read the audio.

    target_resolution:
      Set to (desired_height, desired_width) to have ffmpeg resize the frames
      before returning them. This is much faster than streaming in high-res
      and then resizing. If either dimension is None, the frames are resized
      by keeping the existing aspect ratio.

    resize_algorithm:
      The algorithm used for resizing. Default: "bicubic", other popular
      options include "bilinear" and "fast_bilinear". For more information, see
      https://ffmpeg.org/ffmpeg-scaler.html

    fps_source:
      The fps value to collect from the metadata. Set by default to 'tbr', but
      can be set to 'fps', which may be helpful if importing slow-motion videos
      that get messed up otherwise.


    Attributes
    -----------

    filename:
      Name of the original video file.

    fps:
      Frames per second in the original file.
    
    
    Read docs for Clip() and VideoClip() for other, more generic, attributes.
    
    Lifetime
    --------
    
    Note that this creates subprocesses and locks files. If you construct one of these instances, you must call
    close() afterwards, or the subresources will not be cleaned up until the process ends.
    
    If copies are made, and close() is called on one, it may cause methods on the other copies to fail.  

    """

    def __init__(self, filename, has_mask=False,
                 audio=True, audio_buffersize = 200000,
                 target_resolution=None, resize_algorithm='bicubic',
                 audio_fps=44100, audio_nbytes=2, verbose=False,
                 fps_source='tbr'):

        VideoClip.__init__(self)

        # Make a reader
        pix_fmt= "rgba" if has_mask else "rgb24"
        self.reader = FFMPEG_VideoReader(filename, pix_fmt=pix_fmt,
                                         target_resolution=target_resolution,
                                         resize_algo=resize_algorithm,
                                         fps_source=fps_source)

        # Make some of the reader's attributes accessible from the clip
        self.duration = self.reader.duration
        self.end = self.reader.duration

        self.fps = self.reader.fps
        self.size = self.reader.size
        self.rotation = self.reader.rotation

        self.filename = self.reader.filename

        if has_mask:

            self.make_frame = lambda t: self.reader.get_frame(t)[:,:,:3]
            mask_mf =  lambda t: self.reader.get_frame(t)[:,:,3]/255.0
            self.mask = (VideoClip(ismask = True, make_frame = mask_mf)
                       .set_duration(self.duration))
            self.mask.fps = self.fps

        else:

            self.make_frame = lambda t: self.reader.get_frame(t)

        # Make a reader for the audio, if any.
        if audio and self.reader.infos['audio_found']:

            self.audio = AudioFileClip(filename,
                                       buffersize= audio_buffersize,
                                       fps = audio_fps,
                                       nbytes = audio_nbytes)

    def close(self):
        """ Close the internal reader. """
        if self.reader:
            self.reader.close()
            self.reader = None

        try:
            if self.audio:
                self.audio.close()
                self.audio = None
        except AttributeError:
            pass
示例#9
0
        try:
            my_abs_path = my_file.resolve(strict=True)
        except FileNotFoundError:
            # doesn't exist
            print('ERROR. \n' + ogVid.title + ' was not downloaded')
            errorList.append(ogVid.title)
        else:
            #exists
            #open the video with moviepy and save it to clip the weird silence off of the end
            clip = AudioFileClip(getcwd() + '/.trash/' + vidTitle + '.mp4')
            ffmpeg_audiowrite(clip,
                              getcwd() + '/MusicFiles/' + vidTitle + '.mp4',
                              fps=44100,
                              nbytes=4,
                              buffersize=2000,
                              codec='aac')
            clip.close()
            print(ogVid.title + ' DOWNLOADED')
file.close()
#delete all the useless files in the .trash folder
filelist = [f for f in listdir(getcwd() + '/.trash/') if f.endswith('.mp4')]
for f in filelist:
    remove(path.join(getcwd() + '/.trash/', f))
print('\nPlaylist to MP4 EXITED.')
print('Program ended: ' + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + '\n')

#print out a list of videos that were not downloaded
if len(errorList) > 0:
    print('WARNING. ' + str(len(errorList)) + ' videos not downloaded.')
    for x in errorList:
        print(x + ' was not downloaded.')
示例#10
0
class VideoFileClip(VideoClip):
    """

    A video clip originating from a movie file. For instance: ::

        >>> clip = VideoFileClip("myHolidays.mp4")
        >>> clip.close()
        >>> with VideoFileClip("myMaskVideo.avi") as clip2:
        >>>    pass  # Implicit close called by context manager.


    Parameters
    ------------

    filename:
      The name of the video file. It can have any extension supported
      by ffmpeg: .ogv, .mp4, .mpeg, .avi, .mov etc.

    has_mask:
      Set this to 'True' if there is a mask included in the videofile.
      Video files rarely contain masks, but some video codecs enable
      that. For istance if you have a MoviePy VideoClip with a mask you
      can save it to a videofile with a mask. (see also
      ``VideoClip.write_videofile`` for more details).

    audio:
      Set to `False` if the clip doesn't have any audio or if you do not
      wish to read the audio.

    target_resolution:
      Set to (desired_height, desired_width) to have ffmpeg resize the frames
      before returning them. This is much faster than streaming in high-res
      and then resizing. If either dimension is None, the frames are resized
      by keeping the existing aspect ratio.

    resize_algorithm:
      The algorithm used for resizing. Default: "bicubic", other popular
      options include "bilinear" and "fast_bilinear". For more information, see
      https://ffmpeg.org/ffmpeg-scaler.html

    fps_source:
      The fps value to collect from the metadata. Set by default to 'tbr', but
      can be set to 'fps', which may be helpful if importing slow-motion videos
      that get messed up otherwise.


    Attributes
    -----------

    filename:
      Name of the original video file.

    fps:
      Frames per second in the original file.


    Read docs for Clip() and VideoClip() for other, more generic, attributes.

    Life time
    --------

    Note that this creates sub-processes and locks files. If you construct one of these instances, you must call
    close() afterwards, or the sub-resources will not be cleaned up until the process ends.

    If copies are made, and close() is called on one, it may cause methods on the other copies to fail.

    """
    def __init__(self,
                 filename,
                 has_mask=False,
                 audio=True,
                 audio_buffersize=200000,
                 target_resolution=None,
                 resize_algorithm='bicubic',
                 audio_fps=44100,
                 audio_nbytes=2,
                 verbose=False,
                 fps_source='tbr'):

        VideoClip.__init__(self)

        # Make a reader
        pix_fmt = "rgba" if has_mask else "rgb24"
        self.reader = FFMPEG_VideoReader(filename,
                                         pix_fmt=pix_fmt,
                                         target_resolution=target_resolution,
                                         resize_algo=resize_algorithm,
                                         fps_source=fps_source)

        # Make some of the reader's attributes accessible from the clip
        self.duration = self.reader.duration
        self.end = self.reader.duration

        self.fps = self.reader.fps
        self.size = self.reader.size
        self.rotation = self.reader.rotation

        self.filename = self.reader.filename

        if has_mask:

            self.make_frame = lambda t: self.reader.get_frame(t)[:, :, :3]
            mask_mf = lambda t: self.reader.get_frame(t)[:, :, 3] / 255.0
            self.mask = (VideoClip(
                ismask=True, make_frame=mask_mf).set_duration(self.duration))
            self.mask.fps = self.fps

        else:

            self.make_frame = lambda t: self.reader.get_frame(t)

        # Make a reader for the audio, if any.
        if audio and self.reader.infos['audio_found']:
            self.audio = AudioFileClip(filename,
                                       buffersize=audio_buffersize,
                                       fps=audio_fps,
                                       nbytes=audio_nbytes)

    def close(self):
        """ Close the internal reader. """
        if self.reader:
            self.reader.close()
            self.reader = None

        try:
            if self.audio:
                self.audio.close()
                self.audio = None
        except AttributeError:
            pass
示例#11
0
def mp3_converter(title, url):
    mp3_object = Audio()
    created = False
    mp3_id = None
    special_characters_flag = False
    file_name = title

    print('flag 3')

    print('flag 4')

    # Updating the title to compare it with the mp4 file that exist for it in this folder...
    title = special_characters(title)

    print('flag 5')

    mp3_file = f'{title}.mp3'
    mp4_file = f'{title}.mp4'

    # Converting MP4 to MP3
    clip = AudioFileClip(mp4_file)
    clip.write_audiofile(mp3_file)
    clip.close()

    print('flag 6')

    try:
        # Opening the mp3 file to create an object to store to the database.
        mp3_object.mp3 = File(open(mp3_file, mode='rb'))
        mp3_object.name = file_name
        mp3_object.save()
        mp3_id = mp3_object.pk

        print('flag 7')

        # Deleting the downloaded file after uploading it to cloudinary here...
        if mp4_file in os.listdir(BASE_DIR):
            os.remove(mp4_file)
    
        if mp3_file in os.listdir(BASE_DIR):
            os.remove(mp3_file)

        created = True

        print('flag 8')

    except Exception:
        print("MP4 file cannot open properly")

    # Saving the object to the database.
    if not created:
        # Information that the object wasn't created successfully. We will use this value within the view.py to prevent
        # the programme from crashing.
        special_characters_flag = True

        # Now creating an object to inform administrator what to try and fix to improve the website's functionalities.
        error = TitleError()
        error.name = file_name
        error.url = str(url)
        error.email_sender()
        error.save()
        print('flag 9')
    
    print('flag 10')

    return mp3_id, special_characters_flag
示例#12
0
    def download(self):
        url = self.url_line_edit.text()
        target_dir = self.folder_line_edit.text()
        quality = str(self.quality_combo_box.currentText())
        is_v, is_p = self.is_v(url), self.is_p(url)

        if is_p:
            playlist = pt.Playlist(url)
            iter_things = playlist.video_urls
        elif is_v:
            iter_things = [url]
        else:
            self.url_line_edit.setText("Please enter a valid url")
            return

        if not target_dir:
            self.folder_line_edit.setText("Please choose a directory")
            return

        if quality == SELECT_QUALITY_PROMPT_STRING:
            return

        self.title_label.setText("")
        self.title_label.setText("Downloading..")
        self.message_label.setHidden(False)
        self.quality_combo_box.setHidden(True)
        self.folder_line_edit.setHidden(True)
        self.queue_push_button.setHidden(True)
        self.select_folder_push_button.setHidden(True)
        self.url_line_edit.setHidden(True)
        self.search_push_button.setHidden(True)
        self.start_push_button.setHidden(True)
        n = len(iter_things)

        for ind, text in enumerate(iter_things):
            ext, _, _, qual, vora = quality.split(" ")
            vid = pt.YouTube(text, on_progress_callback=self.update_progress)

            if vora == "resolution":
                self.video_progress_bar.setHidden(False)
                self.message_label.setText(
                    f"Downloading video.. ({ind + 1} of {n})")
                video_path, is_progressive = self.download_video(
                    vid, ext, qual, target_dir)

                if not is_progressive:
                    self.audio_progress_bar.setHidden(False)
                    self.message_label.setText(
                        f"Downloading audio.. ({ind + 1} of {n})")
                    audio_path = self.download_audio(vid, "Highest",
                                                     target_dir, ind, n)
                    self.message_label.setText(
                        f"Merging streams.. ({ind + 1} of {n})")
                    v = VideoFileClip(video_path)
                    a = AudioFileClip(audio_path)
                    v = v.set_audio(a)
                    self.message_label.setText(
                        f"Writing to disk.. ({ind + 1} of {n})")
                    v.write_videofile(f"{video_path[:-4]} - Output.mp4")
                    v.close()
                    a.close()
                    os.remove(audio_path)
                    os.remove(video_path)
                    os.rename(f"{video_path[:-4]} - Output.mp4",
                              f"{video_path[:-4]}.mp4")
                    self.audio_progress_bar.setHidden(True)

                self.video_progress_bar.setHidden(True)

            elif vora == "bitrate":
                self.audio_progress_bar.setHidden(False)
                self.message_label.setText(
                    f"Downloading audio stream.. ({ind + 1} of {n})")
                self.download_audio(vid, qual, target_dir, ind, n)
                self.audio_progress_bar.setHidden(True)

        self.title_label.setText("YouTube Downloader")
        self.message_label.setText("")
        self.message_label.setHidden(True)
        self.queue_push_button.setHidden(False)
        self.url_line_edit.setHidden(False)
        self.search_push_button.setHidden(False)
示例#13
0
def _compile_worker(session_key: str, video_id: str) -> None:
    # Use this for conditional creation
    config = get_nosql_handler().get_video_config(video_id)

    session_clips = get_sql_handler().get_session_clips_by_session_key(session_key)
    for session_clip in session_clips:
        download_session_clip(session_clip, sync=True)
    session_audios = get_sql_handler().get_session_audio_by_session_key(session_key)
    session_audio = None
    if session_audios:
        session_audio = session_audios[0]
        download_session_audio(session_audio, sync=True)

    # Create VideoFileClips
    clips = [VideoFileClip(session_clip.local_file_path()) for session_clip in session_clips]
    total_duration = sum([clip.duration for clip in clips])

    # Make all clips the same size
    final_w = min([clip.w for clip in clips])
    final_h = min([clip.h for clip in clips])
    clips = [resize(clip, newsize=(final_w, final_h)) for clip in clips]

    # Adding gamertag and logo to the video
    # gamertag = config.get('gamertag', '')
    # gamertag_position = config.get('gamertag_position', ['right', 'bottom'])
    #
    # if gamertag != '':
    #     gamertag_clip = TextClip(txt='@'+gamertag, fontsize=50, font = 'Comfortaa', color='white')
    #     gamertag_clip = gamertag_clip.set_duration(final.duration)\
    #                         .margin(right=8,top = 8, left=8, bottom=8, opacity=0)\
    #                         .set_position((gamertag_position[0], gamertag_position[1])

    # === WATERMARK ===
    logo_position = config.get('logo_position', ['left', 'top'])
    logo_clip = ImageClip('./reels/static/reels/reels-logo-white.png')
    logo_clip = resize(logo_clip, height=final_h / 5)
    try:
        logo_x = (0 if logo_position[0] == 'left' else final_w - logo_clip.w)
        logo_y = (0 if logo_position[1] == 'top' else final_h - logo_clip.h)
    except (KeyError, TypeError):
        logo_x, logo_y = 0, final_h - logo_clip.h
    logo_clip = logo_clip.set_pos((logo_x, logo_y))
    clips = [CompositeVideoClip([clip, logo_clip.set_duration(clip.duration)]) for clip in clips]

    # Concatenate clips
    final = concatenate_videoclips(clips, method="compose")

    # Add audio, only if there is audio
    audio_clip = None
    if session_audio:
        audio_clip = AudioFileClip(session_audio.local_file_path())
        audio_clip = audio_clip \
            .set_start(config.get('audio_start', 0)) \
            .set_end(config.get('audio_end', audio_clip.duration))
        # Attach audio to video, but make it only as long as the videos are
        # TODO: Manage case where videos are longer than audio clip
        final = final.set_audio(audio_clip.set_duration(final.duration))

    # If extra editing is enabled, do so
    if config.get('extras', False) and session_audio and get_file_type(session_audio.local_file_path()) == 'wav':
        fs, data = read(session_audio.local_file_path())
        data = data[:, 0]
        data = data[:len(data) - len(data) % 48000]
        data2 = np.mean(data.reshape(-1, int(48000 / 4)), axis=1)
        x = np.diff(data2, n=1)
        secs = np.where(x > 200)[0]
        t = list(secs[np.where(np.diff(secs) > 12)[0] + 1])
        if np.diff(secs)[0] > 12:
            t.insert(0, secs[0])
        for i in range(0, len(t)):
            t[i] /= 4
        for i in t:
            tfreeze = i
            if tfreeze + 1.75 >= final.duration:
                break
            clip_before = final.subclip(t_end=tfreeze)
            clip_after = final.subclip(t_start=tfreeze + 1)
            clip = final.subclip(t_start=tfreeze, t_end=tfreeze + 1)
            if int(i) % 2 == 0:
                clip = clip.fl_image(invert_colors).crossfadein(0.5).crossfadeout(0.5)
            else:
                clip = clip.fx(vfx.painting, saturation=1.6, black=0.006).crossfadein(0.5).crossfadeout(0.5)
            final = concatenate_videoclips([clip_before,
                                            clip,
                                            clip_after])
    else:
        pass

    # === Final Saving ===
    video = get_sql_handler().get_video(video_id)
    final.write_videofile(filename=video.local_file_path(),
                          verbose=True,
                          codec="libx264",
                          audio_codec='aac',
                          temp_audiofile=f'temp-audio-{video.video_id}.m4a',
                          remove_temp=True,
                          preset="medium",
                          ffmpeg_params=["-profile:v", "baseline", "-level", "3.0", "-pix_fmt", "yuv420p"])
    # close local files because we don't need them anymore and so they can be removed later
    for clip in clips:
        clip.close()

    if audio_clip:
        audio_clip.close()
    # upload to cold storage
    save_video(video, sync=True, clean=False)