Converting MP3 albums into MP4 videos for YouTube The Next CEO of Stack OverflowGenerating a thumbnail collage of videosFFmpeg command line for showing two videos side by sideInteractive command line YouTube downloader with option to burn subtitles into videoScript to retrieve new YouTube videosSplit mp3 of album into individual tracksConverting YouTube channels to podcastsffmpeg shell script to squeeze videos for TwitterTesting Video Frames for UniquenessConverting 2D arrays into stringsWeb scraping the titles and descriptions of trending YouTube videos

How to Implement Deterministic Encryption Safely in .NET

What is the process for purifying your home if you believe it may have been previously used for pagan worship?

Is it correct to say moon starry nights?

Computationally populating tables with probability data

Is it ok to trim down a tube patch?

Does higher Oxidation/ reduction potential translate to higher energy storage in battery?

Getting Stale Gas Out of a Gas Tank w/out Dropping the Tank

The Ultimate Number Sequence Puzzle

Traveling with my 5 year old daughter (as the father) without the mother from Germany to Mexico

Cannot shrink btrfs filesystem although there is still data and metadata space left : ERROR: unable to resize '/home': No space left on device

How to use ReplaceAll on an expression that contains a rule

Calculate the Mean mean of two numbers

Small nick on power cord from an electric alarm clock, and copper wiring exposed but intact

Spaces in which all closed sets are regular closed

Is there an equivalent of cd - for cp or mv

how one can write a nice vector parser, something that does pgfvecparseA=B-C; D=E x F;

What was Carter Burke's job for "the company" in Aliens?

My ex-girlfriend uses my Apple ID to login to her iPad, do I have to give her my Apple ID password to reset it?

Defamation due to breach of confidentiality

How do you define an element with an ID attribute using LWC?

Is there such a thing as a proper verb, like a proper noun?

Man transported from Alternate World into ours by a Neutrino Detector

Why do we say 'Un seul M' and not 'Une seule M' even though M is a "consonne"

Why am I getting "Static method cannot be referenced from a non static context: String String.valueOf(Object)"?



Converting MP3 albums into MP4 videos for YouTube



The Next CEO of Stack OverflowGenerating a thumbnail collage of videosFFmpeg command line for showing two videos side by sideInteractive command line YouTube downloader with option to burn subtitles into videoScript to retrieve new YouTube videosSplit mp3 of album into individual tracksConverting YouTube channels to podcastsffmpeg shell script to squeeze videos for TwitterTesting Video Frames for UniquenessConverting 2D arrays into stringsWeb scraping the titles and descriptions of trending YouTube videos










7












$begingroup$


This is a quick script that helps me to convert MP3 files from my CDs into videos I can upload on YouTube. It is intended for personal use, so no input error checks. I am creating a silent video using OpenCV, combining MP3s using pydub, then put audio and video together using ffmpeg.



I am happy with the resulting videos. The code looks a bit naive, but introducing more complex structures is probably an overkill given the simplicity of the task.



This code is also on GitHub.



import os, codecs, datetime, glob
import cv2, pydub, PIL # these packages need to be installed
from PIL import ImageFont, ImageDraw

# get filenames with given extensions from a given directory and all directories inside it
def get_filenames_with_extensions_recursively(directory_name, extensions):
result = []
for extension in extensions:
path_pattern = os.path.join(directory_name, '**', '*.' + extension)
result += glob.glob(path_pattern, recursive=True)
return result

# Score function for default audio sorting: directory containing the file,
# then the number of the track, then the name of the file
def default_func_sort_audio_files(audio_name):
try:
audio_mediainfo = pydub.utils.mediainfo(audio_name).get('TAG', None)
track_str = audio_mediainfo['track']
track_nb_str = track_str.split('/')
track_nb = int(track_nb_str[0])
except:
track_nb = -1
return (os.path.dirname(audio_name), track_nb, os.path.basename(audio_name))

# Resize image, add subtitles and save it.
# Returns the filename of the resulting image (including the path)
def add_subtitles(image_filename,
temp_folder,
width,
height,
subtitles,
font,
sub_colour,
sub_bg_colour,
sub_indent_x):

# make a blank completely transparent image for the rectangle
with PIL.Image.new('RGBA', (width, height), (0, 0, 0, 0)) as img2:
# get a drawing context for it
draw = PIL.ImageDraw.Draw(img2)

# create the background coloured box
max_length_subtitles = 0
for subtitle in subtitles:
sub_size = font.getsize(subtitle)
if max_length_subtitles < sub_size[0]:
max_length_subtitles = sub_size[0]
sub_bg_right = max_length_subtitles + 2 * sub_indent_x
if sub_bg_right > width:
sub_bg_right = width
sub_bg_top = height - len(subtitles) * 2 * font.size - sub_indent_x
draw.rectangle(((0, sub_bg_top), (sub_bg_right, height)), fill = sub_bg_colour)

# add subtitles
sub_indent_y = height
for subtitle in reversed(subtitles):
sub_indent_y -= 2 * font.size
draw.text((sub_indent_x, sub_indent_y), subtitle, sub_colour, font = font)

with PIL.Image.open(image_filename) as img:
img = img.resize((width, height), PIL.Image.ANTIALIAS)
img = img.convert("RGBA")

# composite the two images together and save
temp_image_filename
= os.path.join(temp_folder,
os.path.basename(image_filename) + '_with_subs.png')
with PIL.Image.alpha_composite(img, img2) as img_full:
img_full.save(temp_image_filename)
return temp_image_filename

# The main function. It creates the video with all audio files of a given directory
# All images with given extensions from the same directory are fetched.
# While an audio track is being played, one image, with the subtitles, is shown.
# Images are shown in alphabetic order.
# Audio tracks are sorted using 'func_sort_audio_files'
# Descriptions and subtitles are obtained using 'func_get_audio_description_subtitles'
# Outputs: a compilation video
# a text file that contains the description of the tracks that constitute the video
def make_video( directory_name,
func_get_audio_description_subtitles,
video_title = None,
artist_override = None,
func_sort_audio_files = default_func_sort_audio_files,
width = 1280,
height = 720,
sub_font_size = 32,
sub_font_name = "/System/Library/Fonts/SFNSText.ttf",
sub_encoding = "unic",
sub_colour = (255, 255, 255),
# 4th number in sub_bg_colour is for the degree of transparency, 0 - 255 range
sub_bg_colour = (0, 0, 0, 128),
sub_indent_x = 10,
description_intro = [''],
file_encoding = 'utf-8',
image_extensions = ['jpg', 'png'],
audio_extensions = ['mp3', 'wav'],
dry_run = False):

start_time = datetime.datetime.now()

# prepare the temp directory
temp_folder = os.path.join(directory_name, 'temp')
if not os.path.exists(temp_folder):
os.makedirs(temp_folder)
extensions_to_remove = image_extensions + audio_extensions
if not dry_run:
extensions_to_remove += ['mp4']
filenames_to_remove = get_filenames_with_extensions_recursively(temp_folder, extensions_to_remove)
for fn in filenames_to_remove:
os.remove(fn)

# get the filenames and sort them
images_filenames = get_filenames_with_extensions_recursively(directory_name, image_extensions)
images_filenames.sort()
audio_filenames = get_filenames_with_extensions_recursively(directory_name, audio_extensions)
audio_filenames.sort(key = lambda af: func_sort_audio_files(af))

# initiate variables
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
font = PIL.ImageFont.truetype(sub_font_name, sub_font_size, encoding = sub_encoding)
silent_video_name = os.path.join(temp_folder, os.path.basename(directory_name) + '_silent.mp4')
video = cv2.VideoWriter(silent_video_name, fourcc, 1.0, (width, height))

descriptions = description_intro
audio = pydub.AudioSegment.silent(duration = 0)
counter_audio = 0
counter_seconds = 0

for audio_name in audio_filenames:

audio_mediainfo =
try:
audio_mediainfo = pydub.utils.mediainfo(audio_name).get('TAG', None)
except:
pass

if not ('title' in audio_mediainfo):
track_name = os.path.basename(audio_name)
# remove the extension
track_name = track_name[:track_name.rfind('.')]
audio_mediainfo['title'] = track_name

if not ('artist' in audio_mediainfo):
audio_mediainfo['artist'] = ''
if (artist_override != None):
audio_mediainfo['artist'] = artist_override

counter_audio += 1
description, subtitles = func_get_audio_description_subtitles(counter_audio, audio_mediainfo)
descriptions += [str(datetime.timedelta(seconds=counter_seconds)) + " " + description]

if not dry_run:
image_filename = images_filenames[counter_audio % len(images_filenames)]
temp_image_filename = add_subtitles( image_filename,
temp_folder,
width,
height,
subtitles,
font,
sub_colour,
sub_bg_colour,
sub_indent_x)
img2 = cv2.imread(temp_image_filename)
else:
img2 = None

audio_piece = pydub.AudioSegment.from_mp3(audio_name)
limit_audio_length_so_far = counter_seconds + audio_piece.duration_seconds
while (counter_seconds <= limit_audio_length_so_far):
if not dry_run:
# add the image to the video using PIL (adding by 1sec-long frames)
video.write(img2)
counter_seconds += 1

if not dry_run:
audio += audio_piece
# match the duration of audio and video so far
audio += pydub.AudioSegment.silent(duration = (counter_seconds * 1000.0 - len(audio)))

# Finalize the silent video
cv2.destroyAllWindows()
video.release()

# Define the filenames
if video_title == None:
video_title = os.path.basename(directory_name)
descriptions_file_path = os.path.join(temp_folder, video_title + '.txt')
compilation_audio_name = os.path.join(temp_folder, video_title + '.mp3')
video_name = os.path.join(temp_folder, video_title + '.mp4')
ffmpeg_output_path = os.path.join(temp_folder, video_title + '_ffmpeg.txt')

if not dry_run:
# dump the long mp3
audio.export(compilation_audio_name, format = "mp3")

# combine audio and silent video into the final video
ffmpeg_cmd = 'ffmpeg -i "' + silent_video_name + '" -i "' + compilation_audio_name
+ '" -shortest -c:v copy -c:a aac -b:a 256k "' + video_name + '"'
+ ' > "'+ ffmpeg_output_path + '" 2>&1'
os.system(ffmpeg_cmd)

# Finalize and output the descriptions
descriptions_len = 0
for d_line in descriptions:
descriptions_len += len(d_line)
separator = "*" * 80
descriptions = [separator,
"Directory: " + directory_name,
separator]
+ descriptions
+ [separator,
"The length of the video is " + str(counter_seconds / 60.0) + " minute(s)",
"It should be under 202-205min (this is a pydub limitation)",
separator,
"Description is " + str(descriptions_len) + " characters long",
"It should be under 4500-5000 characters long (this is a youtube limitation)",
separator,
"Started " + str(start_time) + ", completed " + str(datetime.datetime.now()),
separator]
for d_line in descriptions:
print (d_line)
with codecs.open(descriptions_file_path, 'w', encoding = file_encoding) as the_file:
the_file.writelines(d_line + "n" for d_line in (descriptions))

def get_audio_description_subtitles_simple(counter_audio, audio_mediainfo):
title = audio_mediainfo['title'].strip().replace('\', '')
track_name = 'Track ' + str(counter_audio) + ": " + title
artist_name = audio_mediainfo['artist'].strip()
desc = track_name + " by " + artist_name
return desc, [track_name, artist_name]

def dry_run_get_audio_description_subtitles_dry_run(counter_audio, audio_mediainfo):
print(audio_mediainfo)
return "", ""

if __name__ == '__main__':
make_video( directory_name = os.path.expanduser('~/Music/LouisXIII copy'),
#artist_override = 'Dalida',
func_get_audio_description_subtitles = get_audio_description_subtitles_simple,
description_intro = ['Intended for personal use. I own the CDs', ''],
dry_run = True)









share|improve this question











$endgroup$
















    7












    $begingroup$


    This is a quick script that helps me to convert MP3 files from my CDs into videos I can upload on YouTube. It is intended for personal use, so no input error checks. I am creating a silent video using OpenCV, combining MP3s using pydub, then put audio and video together using ffmpeg.



    I am happy with the resulting videos. The code looks a bit naive, but introducing more complex structures is probably an overkill given the simplicity of the task.



    This code is also on GitHub.



    import os, codecs, datetime, glob
    import cv2, pydub, PIL # these packages need to be installed
    from PIL import ImageFont, ImageDraw

    # get filenames with given extensions from a given directory and all directories inside it
    def get_filenames_with_extensions_recursively(directory_name, extensions):
    result = []
    for extension in extensions:
    path_pattern = os.path.join(directory_name, '**', '*.' + extension)
    result += glob.glob(path_pattern, recursive=True)
    return result

    # Score function for default audio sorting: directory containing the file,
    # then the number of the track, then the name of the file
    def default_func_sort_audio_files(audio_name):
    try:
    audio_mediainfo = pydub.utils.mediainfo(audio_name).get('TAG', None)
    track_str = audio_mediainfo['track']
    track_nb_str = track_str.split('/')
    track_nb = int(track_nb_str[0])
    except:
    track_nb = -1
    return (os.path.dirname(audio_name), track_nb, os.path.basename(audio_name))

    # Resize image, add subtitles and save it.
    # Returns the filename of the resulting image (including the path)
    def add_subtitles(image_filename,
    temp_folder,
    width,
    height,
    subtitles,
    font,
    sub_colour,
    sub_bg_colour,
    sub_indent_x):

    # make a blank completely transparent image for the rectangle
    with PIL.Image.new('RGBA', (width, height), (0, 0, 0, 0)) as img2:
    # get a drawing context for it
    draw = PIL.ImageDraw.Draw(img2)

    # create the background coloured box
    max_length_subtitles = 0
    for subtitle in subtitles:
    sub_size = font.getsize(subtitle)
    if max_length_subtitles < sub_size[0]:
    max_length_subtitles = sub_size[0]
    sub_bg_right = max_length_subtitles + 2 * sub_indent_x
    if sub_bg_right > width:
    sub_bg_right = width
    sub_bg_top = height - len(subtitles) * 2 * font.size - sub_indent_x
    draw.rectangle(((0, sub_bg_top), (sub_bg_right, height)), fill = sub_bg_colour)

    # add subtitles
    sub_indent_y = height
    for subtitle in reversed(subtitles):
    sub_indent_y -= 2 * font.size
    draw.text((sub_indent_x, sub_indent_y), subtitle, sub_colour, font = font)

    with PIL.Image.open(image_filename) as img:
    img = img.resize((width, height), PIL.Image.ANTIALIAS)
    img = img.convert("RGBA")

    # composite the two images together and save
    temp_image_filename
    = os.path.join(temp_folder,
    os.path.basename(image_filename) + '_with_subs.png')
    with PIL.Image.alpha_composite(img, img2) as img_full:
    img_full.save(temp_image_filename)
    return temp_image_filename

    # The main function. It creates the video with all audio files of a given directory
    # All images with given extensions from the same directory are fetched.
    # While an audio track is being played, one image, with the subtitles, is shown.
    # Images are shown in alphabetic order.
    # Audio tracks are sorted using 'func_sort_audio_files'
    # Descriptions and subtitles are obtained using 'func_get_audio_description_subtitles'
    # Outputs: a compilation video
    # a text file that contains the description of the tracks that constitute the video
    def make_video( directory_name,
    func_get_audio_description_subtitles,
    video_title = None,
    artist_override = None,
    func_sort_audio_files = default_func_sort_audio_files,
    width = 1280,
    height = 720,
    sub_font_size = 32,
    sub_font_name = "/System/Library/Fonts/SFNSText.ttf",
    sub_encoding = "unic",
    sub_colour = (255, 255, 255),
    # 4th number in sub_bg_colour is for the degree of transparency, 0 - 255 range
    sub_bg_colour = (0, 0, 0, 128),
    sub_indent_x = 10,
    description_intro = [''],
    file_encoding = 'utf-8',
    image_extensions = ['jpg', 'png'],
    audio_extensions = ['mp3', 'wav'],
    dry_run = False):

    start_time = datetime.datetime.now()

    # prepare the temp directory
    temp_folder = os.path.join(directory_name, 'temp')
    if not os.path.exists(temp_folder):
    os.makedirs(temp_folder)
    extensions_to_remove = image_extensions + audio_extensions
    if not dry_run:
    extensions_to_remove += ['mp4']
    filenames_to_remove = get_filenames_with_extensions_recursively(temp_folder, extensions_to_remove)
    for fn in filenames_to_remove:
    os.remove(fn)

    # get the filenames and sort them
    images_filenames = get_filenames_with_extensions_recursively(directory_name, image_extensions)
    images_filenames.sort()
    audio_filenames = get_filenames_with_extensions_recursively(directory_name, audio_extensions)
    audio_filenames.sort(key = lambda af: func_sort_audio_files(af))

    # initiate variables
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    font = PIL.ImageFont.truetype(sub_font_name, sub_font_size, encoding = sub_encoding)
    silent_video_name = os.path.join(temp_folder, os.path.basename(directory_name) + '_silent.mp4')
    video = cv2.VideoWriter(silent_video_name, fourcc, 1.0, (width, height))

    descriptions = description_intro
    audio = pydub.AudioSegment.silent(duration = 0)
    counter_audio = 0
    counter_seconds = 0

    for audio_name in audio_filenames:

    audio_mediainfo =
    try:
    audio_mediainfo = pydub.utils.mediainfo(audio_name).get('TAG', None)
    except:
    pass

    if not ('title' in audio_mediainfo):
    track_name = os.path.basename(audio_name)
    # remove the extension
    track_name = track_name[:track_name.rfind('.')]
    audio_mediainfo['title'] = track_name

    if not ('artist' in audio_mediainfo):
    audio_mediainfo['artist'] = ''
    if (artist_override != None):
    audio_mediainfo['artist'] = artist_override

    counter_audio += 1
    description, subtitles = func_get_audio_description_subtitles(counter_audio, audio_mediainfo)
    descriptions += [str(datetime.timedelta(seconds=counter_seconds)) + " " + description]

    if not dry_run:
    image_filename = images_filenames[counter_audio % len(images_filenames)]
    temp_image_filename = add_subtitles( image_filename,
    temp_folder,
    width,
    height,
    subtitles,
    font,
    sub_colour,
    sub_bg_colour,
    sub_indent_x)
    img2 = cv2.imread(temp_image_filename)
    else:
    img2 = None

    audio_piece = pydub.AudioSegment.from_mp3(audio_name)
    limit_audio_length_so_far = counter_seconds + audio_piece.duration_seconds
    while (counter_seconds <= limit_audio_length_so_far):
    if not dry_run:
    # add the image to the video using PIL (adding by 1sec-long frames)
    video.write(img2)
    counter_seconds += 1

    if not dry_run:
    audio += audio_piece
    # match the duration of audio and video so far
    audio += pydub.AudioSegment.silent(duration = (counter_seconds * 1000.0 - len(audio)))

    # Finalize the silent video
    cv2.destroyAllWindows()
    video.release()

    # Define the filenames
    if video_title == None:
    video_title = os.path.basename(directory_name)
    descriptions_file_path = os.path.join(temp_folder, video_title + '.txt')
    compilation_audio_name = os.path.join(temp_folder, video_title + '.mp3')
    video_name = os.path.join(temp_folder, video_title + '.mp4')
    ffmpeg_output_path = os.path.join(temp_folder, video_title + '_ffmpeg.txt')

    if not dry_run:
    # dump the long mp3
    audio.export(compilation_audio_name, format = "mp3")

    # combine audio and silent video into the final video
    ffmpeg_cmd = 'ffmpeg -i "' + silent_video_name + '" -i "' + compilation_audio_name
    + '" -shortest -c:v copy -c:a aac -b:a 256k "' + video_name + '"'
    + ' > "'+ ffmpeg_output_path + '" 2>&1'
    os.system(ffmpeg_cmd)

    # Finalize and output the descriptions
    descriptions_len = 0
    for d_line in descriptions:
    descriptions_len += len(d_line)
    separator = "*" * 80
    descriptions = [separator,
    "Directory: " + directory_name,
    separator]
    + descriptions
    + [separator,
    "The length of the video is " + str(counter_seconds / 60.0) + " minute(s)",
    "It should be under 202-205min (this is a pydub limitation)",
    separator,
    "Description is " + str(descriptions_len) + " characters long",
    "It should be under 4500-5000 characters long (this is a youtube limitation)",
    separator,
    "Started " + str(start_time) + ", completed " + str(datetime.datetime.now()),
    separator]
    for d_line in descriptions:
    print (d_line)
    with codecs.open(descriptions_file_path, 'w', encoding = file_encoding) as the_file:
    the_file.writelines(d_line + "n" for d_line in (descriptions))

    def get_audio_description_subtitles_simple(counter_audio, audio_mediainfo):
    title = audio_mediainfo['title'].strip().replace('\', '')
    track_name = 'Track ' + str(counter_audio) + ": " + title
    artist_name = audio_mediainfo['artist'].strip()
    desc = track_name + " by " + artist_name
    return desc, [track_name, artist_name]

    def dry_run_get_audio_description_subtitles_dry_run(counter_audio, audio_mediainfo):
    print(audio_mediainfo)
    return "", ""

    if __name__ == '__main__':
    make_video( directory_name = os.path.expanduser('~/Music/LouisXIII copy'),
    #artist_override = 'Dalida',
    func_get_audio_description_subtitles = get_audio_description_subtitles_simple,
    description_intro = ['Intended for personal use. I own the CDs', ''],
    dry_run = True)









    share|improve this question











    $endgroup$














      7












      7








      7


      2



      $begingroup$


      This is a quick script that helps me to convert MP3 files from my CDs into videos I can upload on YouTube. It is intended for personal use, so no input error checks. I am creating a silent video using OpenCV, combining MP3s using pydub, then put audio and video together using ffmpeg.



      I am happy with the resulting videos. The code looks a bit naive, but introducing more complex structures is probably an overkill given the simplicity of the task.



      This code is also on GitHub.



      import os, codecs, datetime, glob
      import cv2, pydub, PIL # these packages need to be installed
      from PIL import ImageFont, ImageDraw

      # get filenames with given extensions from a given directory and all directories inside it
      def get_filenames_with_extensions_recursively(directory_name, extensions):
      result = []
      for extension in extensions:
      path_pattern = os.path.join(directory_name, '**', '*.' + extension)
      result += glob.glob(path_pattern, recursive=True)
      return result

      # Score function for default audio sorting: directory containing the file,
      # then the number of the track, then the name of the file
      def default_func_sort_audio_files(audio_name):
      try:
      audio_mediainfo = pydub.utils.mediainfo(audio_name).get('TAG', None)
      track_str = audio_mediainfo['track']
      track_nb_str = track_str.split('/')
      track_nb = int(track_nb_str[0])
      except:
      track_nb = -1
      return (os.path.dirname(audio_name), track_nb, os.path.basename(audio_name))

      # Resize image, add subtitles and save it.
      # Returns the filename of the resulting image (including the path)
      def add_subtitles(image_filename,
      temp_folder,
      width,
      height,
      subtitles,
      font,
      sub_colour,
      sub_bg_colour,
      sub_indent_x):

      # make a blank completely transparent image for the rectangle
      with PIL.Image.new('RGBA', (width, height), (0, 0, 0, 0)) as img2:
      # get a drawing context for it
      draw = PIL.ImageDraw.Draw(img2)

      # create the background coloured box
      max_length_subtitles = 0
      for subtitle in subtitles:
      sub_size = font.getsize(subtitle)
      if max_length_subtitles < sub_size[0]:
      max_length_subtitles = sub_size[0]
      sub_bg_right = max_length_subtitles + 2 * sub_indent_x
      if sub_bg_right > width:
      sub_bg_right = width
      sub_bg_top = height - len(subtitles) * 2 * font.size - sub_indent_x
      draw.rectangle(((0, sub_bg_top), (sub_bg_right, height)), fill = sub_bg_colour)

      # add subtitles
      sub_indent_y = height
      for subtitle in reversed(subtitles):
      sub_indent_y -= 2 * font.size
      draw.text((sub_indent_x, sub_indent_y), subtitle, sub_colour, font = font)

      with PIL.Image.open(image_filename) as img:
      img = img.resize((width, height), PIL.Image.ANTIALIAS)
      img = img.convert("RGBA")

      # composite the two images together and save
      temp_image_filename
      = os.path.join(temp_folder,
      os.path.basename(image_filename) + '_with_subs.png')
      with PIL.Image.alpha_composite(img, img2) as img_full:
      img_full.save(temp_image_filename)
      return temp_image_filename

      # The main function. It creates the video with all audio files of a given directory
      # All images with given extensions from the same directory are fetched.
      # While an audio track is being played, one image, with the subtitles, is shown.
      # Images are shown in alphabetic order.
      # Audio tracks are sorted using 'func_sort_audio_files'
      # Descriptions and subtitles are obtained using 'func_get_audio_description_subtitles'
      # Outputs: a compilation video
      # a text file that contains the description of the tracks that constitute the video
      def make_video( directory_name,
      func_get_audio_description_subtitles,
      video_title = None,
      artist_override = None,
      func_sort_audio_files = default_func_sort_audio_files,
      width = 1280,
      height = 720,
      sub_font_size = 32,
      sub_font_name = "/System/Library/Fonts/SFNSText.ttf",
      sub_encoding = "unic",
      sub_colour = (255, 255, 255),
      # 4th number in sub_bg_colour is for the degree of transparency, 0 - 255 range
      sub_bg_colour = (0, 0, 0, 128),
      sub_indent_x = 10,
      description_intro = [''],
      file_encoding = 'utf-8',
      image_extensions = ['jpg', 'png'],
      audio_extensions = ['mp3', 'wav'],
      dry_run = False):

      start_time = datetime.datetime.now()

      # prepare the temp directory
      temp_folder = os.path.join(directory_name, 'temp')
      if not os.path.exists(temp_folder):
      os.makedirs(temp_folder)
      extensions_to_remove = image_extensions + audio_extensions
      if not dry_run:
      extensions_to_remove += ['mp4']
      filenames_to_remove = get_filenames_with_extensions_recursively(temp_folder, extensions_to_remove)
      for fn in filenames_to_remove:
      os.remove(fn)

      # get the filenames and sort them
      images_filenames = get_filenames_with_extensions_recursively(directory_name, image_extensions)
      images_filenames.sort()
      audio_filenames = get_filenames_with_extensions_recursively(directory_name, audio_extensions)
      audio_filenames.sort(key = lambda af: func_sort_audio_files(af))

      # initiate variables
      fourcc = cv2.VideoWriter_fourcc(*'mp4v')
      font = PIL.ImageFont.truetype(sub_font_name, sub_font_size, encoding = sub_encoding)
      silent_video_name = os.path.join(temp_folder, os.path.basename(directory_name) + '_silent.mp4')
      video = cv2.VideoWriter(silent_video_name, fourcc, 1.0, (width, height))

      descriptions = description_intro
      audio = pydub.AudioSegment.silent(duration = 0)
      counter_audio = 0
      counter_seconds = 0

      for audio_name in audio_filenames:

      audio_mediainfo =
      try:
      audio_mediainfo = pydub.utils.mediainfo(audio_name).get('TAG', None)
      except:
      pass

      if not ('title' in audio_mediainfo):
      track_name = os.path.basename(audio_name)
      # remove the extension
      track_name = track_name[:track_name.rfind('.')]
      audio_mediainfo['title'] = track_name

      if not ('artist' in audio_mediainfo):
      audio_mediainfo['artist'] = ''
      if (artist_override != None):
      audio_mediainfo['artist'] = artist_override

      counter_audio += 1
      description, subtitles = func_get_audio_description_subtitles(counter_audio, audio_mediainfo)
      descriptions += [str(datetime.timedelta(seconds=counter_seconds)) + " " + description]

      if not dry_run:
      image_filename = images_filenames[counter_audio % len(images_filenames)]
      temp_image_filename = add_subtitles( image_filename,
      temp_folder,
      width,
      height,
      subtitles,
      font,
      sub_colour,
      sub_bg_colour,
      sub_indent_x)
      img2 = cv2.imread(temp_image_filename)
      else:
      img2 = None

      audio_piece = pydub.AudioSegment.from_mp3(audio_name)
      limit_audio_length_so_far = counter_seconds + audio_piece.duration_seconds
      while (counter_seconds <= limit_audio_length_so_far):
      if not dry_run:
      # add the image to the video using PIL (adding by 1sec-long frames)
      video.write(img2)
      counter_seconds += 1

      if not dry_run:
      audio += audio_piece
      # match the duration of audio and video so far
      audio += pydub.AudioSegment.silent(duration = (counter_seconds * 1000.0 - len(audio)))

      # Finalize the silent video
      cv2.destroyAllWindows()
      video.release()

      # Define the filenames
      if video_title == None:
      video_title = os.path.basename(directory_name)
      descriptions_file_path = os.path.join(temp_folder, video_title + '.txt')
      compilation_audio_name = os.path.join(temp_folder, video_title + '.mp3')
      video_name = os.path.join(temp_folder, video_title + '.mp4')
      ffmpeg_output_path = os.path.join(temp_folder, video_title + '_ffmpeg.txt')

      if not dry_run:
      # dump the long mp3
      audio.export(compilation_audio_name, format = "mp3")

      # combine audio and silent video into the final video
      ffmpeg_cmd = 'ffmpeg -i "' + silent_video_name + '" -i "' + compilation_audio_name
      + '" -shortest -c:v copy -c:a aac -b:a 256k "' + video_name + '"'
      + ' > "'+ ffmpeg_output_path + '" 2>&1'
      os.system(ffmpeg_cmd)

      # Finalize and output the descriptions
      descriptions_len = 0
      for d_line in descriptions:
      descriptions_len += len(d_line)
      separator = "*" * 80
      descriptions = [separator,
      "Directory: " + directory_name,
      separator]
      + descriptions
      + [separator,
      "The length of the video is " + str(counter_seconds / 60.0) + " minute(s)",
      "It should be under 202-205min (this is a pydub limitation)",
      separator,
      "Description is " + str(descriptions_len) + " characters long",
      "It should be under 4500-5000 characters long (this is a youtube limitation)",
      separator,
      "Started " + str(start_time) + ", completed " + str(datetime.datetime.now()),
      separator]
      for d_line in descriptions:
      print (d_line)
      with codecs.open(descriptions_file_path, 'w', encoding = file_encoding) as the_file:
      the_file.writelines(d_line + "n" for d_line in (descriptions))

      def get_audio_description_subtitles_simple(counter_audio, audio_mediainfo):
      title = audio_mediainfo['title'].strip().replace('\', '')
      track_name = 'Track ' + str(counter_audio) + ": " + title
      artist_name = audio_mediainfo['artist'].strip()
      desc = track_name + " by " + artist_name
      return desc, [track_name, artist_name]

      def dry_run_get_audio_description_subtitles_dry_run(counter_audio, audio_mediainfo):
      print(audio_mediainfo)
      return "", ""

      if __name__ == '__main__':
      make_video( directory_name = os.path.expanduser('~/Music/LouisXIII copy'),
      #artist_override = 'Dalida',
      func_get_audio_description_subtitles = get_audio_description_subtitles_simple,
      description_intro = ['Intended for personal use. I own the CDs', ''],
      dry_run = True)









      share|improve this question











      $endgroup$




      This is a quick script that helps me to convert MP3 files from my CDs into videos I can upload on YouTube. It is intended for personal use, so no input error checks. I am creating a silent video using OpenCV, combining MP3s using pydub, then put audio and video together using ffmpeg.



      I am happy with the resulting videos. The code looks a bit naive, but introducing more complex structures is probably an overkill given the simplicity of the task.



      This code is also on GitHub.



      import os, codecs, datetime, glob
      import cv2, pydub, PIL # these packages need to be installed
      from PIL import ImageFont, ImageDraw

      # get filenames with given extensions from a given directory and all directories inside it
      def get_filenames_with_extensions_recursively(directory_name, extensions):
      result = []
      for extension in extensions:
      path_pattern = os.path.join(directory_name, '**', '*.' + extension)
      result += glob.glob(path_pattern, recursive=True)
      return result

      # Score function for default audio sorting: directory containing the file,
      # then the number of the track, then the name of the file
      def default_func_sort_audio_files(audio_name):
      try:
      audio_mediainfo = pydub.utils.mediainfo(audio_name).get('TAG', None)
      track_str = audio_mediainfo['track']
      track_nb_str = track_str.split('/')
      track_nb = int(track_nb_str[0])
      except:
      track_nb = -1
      return (os.path.dirname(audio_name), track_nb, os.path.basename(audio_name))

      # Resize image, add subtitles and save it.
      # Returns the filename of the resulting image (including the path)
      def add_subtitles(image_filename,
      temp_folder,
      width,
      height,
      subtitles,
      font,
      sub_colour,
      sub_bg_colour,
      sub_indent_x):

      # make a blank completely transparent image for the rectangle
      with PIL.Image.new('RGBA', (width, height), (0, 0, 0, 0)) as img2:
      # get a drawing context for it
      draw = PIL.ImageDraw.Draw(img2)

      # create the background coloured box
      max_length_subtitles = 0
      for subtitle in subtitles:
      sub_size = font.getsize(subtitle)
      if max_length_subtitles < sub_size[0]:
      max_length_subtitles = sub_size[0]
      sub_bg_right = max_length_subtitles + 2 * sub_indent_x
      if sub_bg_right > width:
      sub_bg_right = width
      sub_bg_top = height - len(subtitles) * 2 * font.size - sub_indent_x
      draw.rectangle(((0, sub_bg_top), (sub_bg_right, height)), fill = sub_bg_colour)

      # add subtitles
      sub_indent_y = height
      for subtitle in reversed(subtitles):
      sub_indent_y -= 2 * font.size
      draw.text((sub_indent_x, sub_indent_y), subtitle, sub_colour, font = font)

      with PIL.Image.open(image_filename) as img:
      img = img.resize((width, height), PIL.Image.ANTIALIAS)
      img = img.convert("RGBA")

      # composite the two images together and save
      temp_image_filename
      = os.path.join(temp_folder,
      os.path.basename(image_filename) + '_with_subs.png')
      with PIL.Image.alpha_composite(img, img2) as img_full:
      img_full.save(temp_image_filename)
      return temp_image_filename

      # The main function. It creates the video with all audio files of a given directory
      # All images with given extensions from the same directory are fetched.
      # While an audio track is being played, one image, with the subtitles, is shown.
      # Images are shown in alphabetic order.
      # Audio tracks are sorted using 'func_sort_audio_files'
      # Descriptions and subtitles are obtained using 'func_get_audio_description_subtitles'
      # Outputs: a compilation video
      # a text file that contains the description of the tracks that constitute the video
      def make_video( directory_name,
      func_get_audio_description_subtitles,
      video_title = None,
      artist_override = None,
      func_sort_audio_files = default_func_sort_audio_files,
      width = 1280,
      height = 720,
      sub_font_size = 32,
      sub_font_name = "/System/Library/Fonts/SFNSText.ttf",
      sub_encoding = "unic",
      sub_colour = (255, 255, 255),
      # 4th number in sub_bg_colour is for the degree of transparency, 0 - 255 range
      sub_bg_colour = (0, 0, 0, 128),
      sub_indent_x = 10,
      description_intro = [''],
      file_encoding = 'utf-8',
      image_extensions = ['jpg', 'png'],
      audio_extensions = ['mp3', 'wav'],
      dry_run = False):

      start_time = datetime.datetime.now()

      # prepare the temp directory
      temp_folder = os.path.join(directory_name, 'temp')
      if not os.path.exists(temp_folder):
      os.makedirs(temp_folder)
      extensions_to_remove = image_extensions + audio_extensions
      if not dry_run:
      extensions_to_remove += ['mp4']
      filenames_to_remove = get_filenames_with_extensions_recursively(temp_folder, extensions_to_remove)
      for fn in filenames_to_remove:
      os.remove(fn)

      # get the filenames and sort them
      images_filenames = get_filenames_with_extensions_recursively(directory_name, image_extensions)
      images_filenames.sort()
      audio_filenames = get_filenames_with_extensions_recursively(directory_name, audio_extensions)
      audio_filenames.sort(key = lambda af: func_sort_audio_files(af))

      # initiate variables
      fourcc = cv2.VideoWriter_fourcc(*'mp4v')
      font = PIL.ImageFont.truetype(sub_font_name, sub_font_size, encoding = sub_encoding)
      silent_video_name = os.path.join(temp_folder, os.path.basename(directory_name) + '_silent.mp4')
      video = cv2.VideoWriter(silent_video_name, fourcc, 1.0, (width, height))

      descriptions = description_intro
      audio = pydub.AudioSegment.silent(duration = 0)
      counter_audio = 0
      counter_seconds = 0

      for audio_name in audio_filenames:

      audio_mediainfo =
      try:
      audio_mediainfo = pydub.utils.mediainfo(audio_name).get('TAG', None)
      except:
      pass

      if not ('title' in audio_mediainfo):
      track_name = os.path.basename(audio_name)
      # remove the extension
      track_name = track_name[:track_name.rfind('.')]
      audio_mediainfo['title'] = track_name

      if not ('artist' in audio_mediainfo):
      audio_mediainfo['artist'] = ''
      if (artist_override != None):
      audio_mediainfo['artist'] = artist_override

      counter_audio += 1
      description, subtitles = func_get_audio_description_subtitles(counter_audio, audio_mediainfo)
      descriptions += [str(datetime.timedelta(seconds=counter_seconds)) + " " + description]

      if not dry_run:
      image_filename = images_filenames[counter_audio % len(images_filenames)]
      temp_image_filename = add_subtitles( image_filename,
      temp_folder,
      width,
      height,
      subtitles,
      font,
      sub_colour,
      sub_bg_colour,
      sub_indent_x)
      img2 = cv2.imread(temp_image_filename)
      else:
      img2 = None

      audio_piece = pydub.AudioSegment.from_mp3(audio_name)
      limit_audio_length_so_far = counter_seconds + audio_piece.duration_seconds
      while (counter_seconds <= limit_audio_length_so_far):
      if not dry_run:
      # add the image to the video using PIL (adding by 1sec-long frames)
      video.write(img2)
      counter_seconds += 1

      if not dry_run:
      audio += audio_piece
      # match the duration of audio and video so far
      audio += pydub.AudioSegment.silent(duration = (counter_seconds * 1000.0 - len(audio)))

      # Finalize the silent video
      cv2.destroyAllWindows()
      video.release()

      # Define the filenames
      if video_title == None:
      video_title = os.path.basename(directory_name)
      descriptions_file_path = os.path.join(temp_folder, video_title + '.txt')
      compilation_audio_name = os.path.join(temp_folder, video_title + '.mp3')
      video_name = os.path.join(temp_folder, video_title + '.mp4')
      ffmpeg_output_path = os.path.join(temp_folder, video_title + '_ffmpeg.txt')

      if not dry_run:
      # dump the long mp3
      audio.export(compilation_audio_name, format = "mp3")

      # combine audio and silent video into the final video
      ffmpeg_cmd = 'ffmpeg -i "' + silent_video_name + '" -i "' + compilation_audio_name
      + '" -shortest -c:v copy -c:a aac -b:a 256k "' + video_name + '"'
      + ' > "'+ ffmpeg_output_path + '" 2>&1'
      os.system(ffmpeg_cmd)

      # Finalize and output the descriptions
      descriptions_len = 0
      for d_line in descriptions:
      descriptions_len += len(d_line)
      separator = "*" * 80
      descriptions = [separator,
      "Directory: " + directory_name,
      separator]
      + descriptions
      + [separator,
      "The length of the video is " + str(counter_seconds / 60.0) + " minute(s)",
      "It should be under 202-205min (this is a pydub limitation)",
      separator,
      "Description is " + str(descriptions_len) + " characters long",
      "It should be under 4500-5000 characters long (this is a youtube limitation)",
      separator,
      "Started " + str(start_time) + ", completed " + str(datetime.datetime.now()),
      separator]
      for d_line in descriptions:
      print (d_line)
      with codecs.open(descriptions_file_path, 'w', encoding = file_encoding) as the_file:
      the_file.writelines(d_line + "n" for d_line in (descriptions))

      def get_audio_description_subtitles_simple(counter_audio, audio_mediainfo):
      title = audio_mediainfo['title'].strip().replace('\', '')
      track_name = 'Track ' + str(counter_audio) + ": " + title
      artist_name = audio_mediainfo['artist'].strip()
      desc = track_name + " by " + artist_name
      return desc, [track_name, artist_name]

      def dry_run_get_audio_description_subtitles_dry_run(counter_audio, audio_mediainfo):
      print(audio_mediainfo)
      return "", ""

      if __name__ == '__main__':
      make_video( directory_name = os.path.expanduser('~/Music/LouisXIII copy'),
      #artist_override = 'Dalida',
      func_get_audio_description_subtitles = get_audio_description_subtitles_simple,
      description_intro = ['Intended for personal use. I own the CDs', ''],
      dry_run = True)






      python python-3.x opencv video






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Apr 27 '18 at 2:33









      Jamal

      30.5k11121227




      30.5k11121227










      asked Apr 3 '18 at 22:09









      Yulia VYulia V

      272210




      272210




















          1 Answer
          1






          active

          oldest

          votes


















          0












          $begingroup$

          import os, codecs, datetime, glob

          E401 multiple imports on one line


          Recommend you run $ flake8 and heed its advice,
          as PEP-8 asks for just one import per line.
          Use isort to organize them.



          Each of your functions has lovely comments; thank you.
          Recommend you turn the one-sentence comments into docstrings.



          The add_subtitles() function is maybe slightly long,
          and could be broken out into one or two helpers.
          The arg list is on the long side.
          Width + height could trivially be collapsed into size,
          but I wonder if some of the other attributes, like colour,
          might sensibly be defaulted from an object that has an add_subtitles() method.



           if max_length_subtitles < sub_size[0]:
          max_length_subtitles = sub_size[0]


          A more pythonic way to express this would be to
          construct a list of font sizes, and then
          assign max( ... ) of those sizes.



          Similarly, please assign sub_bg_right as max of two numbers.



          It feels like much of this logic could sensibly be encapsulated
          within a sub_bg object.



           draw.text(..., font = font)


          PEP-8 asks for spaces around = assignment, but no spaces around = keyword args: font=font.



          Nice comments on make_video().
          Again, it takes quite a few args.
          It feels like a subtitle object could encapsulate several of them.
          These args are trouble:



           description_intro = [''],
          file_encoding = 'utf-8',
          image_extensions = ['jpg', 'png'],
          audio_extensions = ['mp3', 'wav'],


          Well, file_encoding is fine, just lose the extra blanks around = equals.
          But the lists are trouble.
          Now, I know you're not mutating them.
          But it's a gotcha, evaluating and binding a mutable list at function definition time.
          Don't get in the habit of doing that.
          Make it default to an immutable sequence, such as a (tuple),
          or use the usual idiom:



          def foo(name, extensions=None):
          if extensions is None:
          extensions = ['jpg', 'png']


          The point is to re-evaluate the assignment on each execution of foo(),
          rather than binding an immortal list just once.



          if not os.path.exists(temp_folder):
          os.makedirs(temp_folder)


          Feel free to save one line by specifying , exist_ok=True.



          You could easily break out a few helpers from this function,
          for example the whole audio_filenames loop is naturally a helper function.



           if not ('artist' in audio_mediainfo):


          That's fine, but testing if 'artist' not in audio_mediainfo: is
          slightly more pythonic.



           if (artist_override != None): 


          1. No need for ( extra parens ) in a python if.


          2. Please test is identity of the None singleton, rather than equality: if artist_override is not None:. Or, more simply: if artist_override:



            while (counter_seconds <= limit_audio_length_so_far):


          No ( extra parens ) in a python while, please.



          if video_title == None:


          Please test is None.



           "It should be under 202-205min (this is a pydub limitation)",
          ...
          "It should be under 4500-5000 characters long (this is a youtube limitation)",


          There are limits, but you're not telling me what they are.
          Put a stake in the ground, say 202 min. and 4500 char. and be done with it.



          title = audio_mediainfo['title'].strip().replace('\', '')


          This is apparently sanitizing a filename, making it safe to open for write.
          That is, you are rejecting Known Bad unicode code points.
          I would be more convinced if you instead used maketrans and translate to
          accept Known Good characters.



          You tend to accommodate long expressions with backwhack continuation characters.
          Consider using ( parens ) instead:



          short_string = 'Hi!'
          much_longer_string = ('This,'
          ' that,'
          ' and the other.')





          share|improve this answer









          $endgroup$













            Your Answer





            StackExchange.ifUsing("editor", function ()
            return StackExchange.using("mathjaxEditing", function ()
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            );
            );
            , "mathjax-editing");

            StackExchange.ifUsing("editor", function ()
            StackExchange.using("externalEditor", function ()
            StackExchange.using("snippets", function ()
            StackExchange.snippets.init();
            );
            );
            , "code-snippets");

            StackExchange.ready(function()
            var channelOptions =
            tags: "".split(" "),
            id: "196"
            ;
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function()
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled)
            StackExchange.using("snippets", function()
            createEditor();
            );

            else
            createEditor();

            );

            function createEditor()
            StackExchange.prepareEditor(
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: false,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            imageUploader:
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            ,
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            );



            );













            draft saved

            draft discarded


















            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191204%2fconverting-mp3-albums-into-mp4-videos-for-youtube%23new-answer', 'question_page');

            );

            Post as a guest















            Required, but never shown

























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            0












            $begingroup$

            import os, codecs, datetime, glob

            E401 multiple imports on one line


            Recommend you run $ flake8 and heed its advice,
            as PEP-8 asks for just one import per line.
            Use isort to organize them.



            Each of your functions has lovely comments; thank you.
            Recommend you turn the one-sentence comments into docstrings.



            The add_subtitles() function is maybe slightly long,
            and could be broken out into one or two helpers.
            The arg list is on the long side.
            Width + height could trivially be collapsed into size,
            but I wonder if some of the other attributes, like colour,
            might sensibly be defaulted from an object that has an add_subtitles() method.



             if max_length_subtitles < sub_size[0]:
            max_length_subtitles = sub_size[0]


            A more pythonic way to express this would be to
            construct a list of font sizes, and then
            assign max( ... ) of those sizes.



            Similarly, please assign sub_bg_right as max of two numbers.



            It feels like much of this logic could sensibly be encapsulated
            within a sub_bg object.



             draw.text(..., font = font)


            PEP-8 asks for spaces around = assignment, but no spaces around = keyword args: font=font.



            Nice comments on make_video().
            Again, it takes quite a few args.
            It feels like a subtitle object could encapsulate several of them.
            These args are trouble:



             description_intro = [''],
            file_encoding = 'utf-8',
            image_extensions = ['jpg', 'png'],
            audio_extensions = ['mp3', 'wav'],


            Well, file_encoding is fine, just lose the extra blanks around = equals.
            But the lists are trouble.
            Now, I know you're not mutating them.
            But it's a gotcha, evaluating and binding a mutable list at function definition time.
            Don't get in the habit of doing that.
            Make it default to an immutable sequence, such as a (tuple),
            or use the usual idiom:



            def foo(name, extensions=None):
            if extensions is None:
            extensions = ['jpg', 'png']


            The point is to re-evaluate the assignment on each execution of foo(),
            rather than binding an immortal list just once.



            if not os.path.exists(temp_folder):
            os.makedirs(temp_folder)


            Feel free to save one line by specifying , exist_ok=True.



            You could easily break out a few helpers from this function,
            for example the whole audio_filenames loop is naturally a helper function.



             if not ('artist' in audio_mediainfo):


            That's fine, but testing if 'artist' not in audio_mediainfo: is
            slightly more pythonic.



             if (artist_override != None): 


            1. No need for ( extra parens ) in a python if.


            2. Please test is identity of the None singleton, rather than equality: if artist_override is not None:. Or, more simply: if artist_override:



              while (counter_seconds <= limit_audio_length_so_far):


            No ( extra parens ) in a python while, please.



            if video_title == None:


            Please test is None.



             "It should be under 202-205min (this is a pydub limitation)",
            ...
            "It should be under 4500-5000 characters long (this is a youtube limitation)",


            There are limits, but you're not telling me what they are.
            Put a stake in the ground, say 202 min. and 4500 char. and be done with it.



            title = audio_mediainfo['title'].strip().replace('\', '')


            This is apparently sanitizing a filename, making it safe to open for write.
            That is, you are rejecting Known Bad unicode code points.
            I would be more convinced if you instead used maketrans and translate to
            accept Known Good characters.



            You tend to accommodate long expressions with backwhack continuation characters.
            Consider using ( parens ) instead:



            short_string = 'Hi!'
            much_longer_string = ('This,'
            ' that,'
            ' and the other.')





            share|improve this answer









            $endgroup$

















              0












              $begingroup$

              import os, codecs, datetime, glob

              E401 multiple imports on one line


              Recommend you run $ flake8 and heed its advice,
              as PEP-8 asks for just one import per line.
              Use isort to organize them.



              Each of your functions has lovely comments; thank you.
              Recommend you turn the one-sentence comments into docstrings.



              The add_subtitles() function is maybe slightly long,
              and could be broken out into one or two helpers.
              The arg list is on the long side.
              Width + height could trivially be collapsed into size,
              but I wonder if some of the other attributes, like colour,
              might sensibly be defaulted from an object that has an add_subtitles() method.



               if max_length_subtitles < sub_size[0]:
              max_length_subtitles = sub_size[0]


              A more pythonic way to express this would be to
              construct a list of font sizes, and then
              assign max( ... ) of those sizes.



              Similarly, please assign sub_bg_right as max of two numbers.



              It feels like much of this logic could sensibly be encapsulated
              within a sub_bg object.



               draw.text(..., font = font)


              PEP-8 asks for spaces around = assignment, but no spaces around = keyword args: font=font.



              Nice comments on make_video().
              Again, it takes quite a few args.
              It feels like a subtitle object could encapsulate several of them.
              These args are trouble:



               description_intro = [''],
              file_encoding = 'utf-8',
              image_extensions = ['jpg', 'png'],
              audio_extensions = ['mp3', 'wav'],


              Well, file_encoding is fine, just lose the extra blanks around = equals.
              But the lists are trouble.
              Now, I know you're not mutating them.
              But it's a gotcha, evaluating and binding a mutable list at function definition time.
              Don't get in the habit of doing that.
              Make it default to an immutable sequence, such as a (tuple),
              or use the usual idiom:



              def foo(name, extensions=None):
              if extensions is None:
              extensions = ['jpg', 'png']


              The point is to re-evaluate the assignment on each execution of foo(),
              rather than binding an immortal list just once.



              if not os.path.exists(temp_folder):
              os.makedirs(temp_folder)


              Feel free to save one line by specifying , exist_ok=True.



              You could easily break out a few helpers from this function,
              for example the whole audio_filenames loop is naturally a helper function.



               if not ('artist' in audio_mediainfo):


              That's fine, but testing if 'artist' not in audio_mediainfo: is
              slightly more pythonic.



               if (artist_override != None): 


              1. No need for ( extra parens ) in a python if.


              2. Please test is identity of the None singleton, rather than equality: if artist_override is not None:. Or, more simply: if artist_override:



                while (counter_seconds <= limit_audio_length_so_far):


              No ( extra parens ) in a python while, please.



              if video_title == None:


              Please test is None.



               "It should be under 202-205min (this is a pydub limitation)",
              ...
              "It should be under 4500-5000 characters long (this is a youtube limitation)",


              There are limits, but you're not telling me what they are.
              Put a stake in the ground, say 202 min. and 4500 char. and be done with it.



              title = audio_mediainfo['title'].strip().replace('\', '')


              This is apparently sanitizing a filename, making it safe to open for write.
              That is, you are rejecting Known Bad unicode code points.
              I would be more convinced if you instead used maketrans and translate to
              accept Known Good characters.



              You tend to accommodate long expressions with backwhack continuation characters.
              Consider using ( parens ) instead:



              short_string = 'Hi!'
              much_longer_string = ('This,'
              ' that,'
              ' and the other.')





              share|improve this answer









              $endgroup$















                0












                0








                0





                $begingroup$

                import os, codecs, datetime, glob

                E401 multiple imports on one line


                Recommend you run $ flake8 and heed its advice,
                as PEP-8 asks for just one import per line.
                Use isort to organize them.



                Each of your functions has lovely comments; thank you.
                Recommend you turn the one-sentence comments into docstrings.



                The add_subtitles() function is maybe slightly long,
                and could be broken out into one or two helpers.
                The arg list is on the long side.
                Width + height could trivially be collapsed into size,
                but I wonder if some of the other attributes, like colour,
                might sensibly be defaulted from an object that has an add_subtitles() method.



                 if max_length_subtitles < sub_size[0]:
                max_length_subtitles = sub_size[0]


                A more pythonic way to express this would be to
                construct a list of font sizes, and then
                assign max( ... ) of those sizes.



                Similarly, please assign sub_bg_right as max of two numbers.



                It feels like much of this logic could sensibly be encapsulated
                within a sub_bg object.



                 draw.text(..., font = font)


                PEP-8 asks for spaces around = assignment, but no spaces around = keyword args: font=font.



                Nice comments on make_video().
                Again, it takes quite a few args.
                It feels like a subtitle object could encapsulate several of them.
                These args are trouble:



                 description_intro = [''],
                file_encoding = 'utf-8',
                image_extensions = ['jpg', 'png'],
                audio_extensions = ['mp3', 'wav'],


                Well, file_encoding is fine, just lose the extra blanks around = equals.
                But the lists are trouble.
                Now, I know you're not mutating them.
                But it's a gotcha, evaluating and binding a mutable list at function definition time.
                Don't get in the habit of doing that.
                Make it default to an immutable sequence, such as a (tuple),
                or use the usual idiom:



                def foo(name, extensions=None):
                if extensions is None:
                extensions = ['jpg', 'png']


                The point is to re-evaluate the assignment on each execution of foo(),
                rather than binding an immortal list just once.



                if not os.path.exists(temp_folder):
                os.makedirs(temp_folder)


                Feel free to save one line by specifying , exist_ok=True.



                You could easily break out a few helpers from this function,
                for example the whole audio_filenames loop is naturally a helper function.



                 if not ('artist' in audio_mediainfo):


                That's fine, but testing if 'artist' not in audio_mediainfo: is
                slightly more pythonic.



                 if (artist_override != None): 


                1. No need for ( extra parens ) in a python if.


                2. Please test is identity of the None singleton, rather than equality: if artist_override is not None:. Or, more simply: if artist_override:



                  while (counter_seconds <= limit_audio_length_so_far):


                No ( extra parens ) in a python while, please.



                if video_title == None:


                Please test is None.



                 "It should be under 202-205min (this is a pydub limitation)",
                ...
                "It should be under 4500-5000 characters long (this is a youtube limitation)",


                There are limits, but you're not telling me what they are.
                Put a stake in the ground, say 202 min. and 4500 char. and be done with it.



                title = audio_mediainfo['title'].strip().replace('\', '')


                This is apparently sanitizing a filename, making it safe to open for write.
                That is, you are rejecting Known Bad unicode code points.
                I would be more convinced if you instead used maketrans and translate to
                accept Known Good characters.



                You tend to accommodate long expressions with backwhack continuation characters.
                Consider using ( parens ) instead:



                short_string = 'Hi!'
                much_longer_string = ('This,'
                ' that,'
                ' and the other.')





                share|improve this answer









                $endgroup$



                import os, codecs, datetime, glob

                E401 multiple imports on one line


                Recommend you run $ flake8 and heed its advice,
                as PEP-8 asks for just one import per line.
                Use isort to organize them.



                Each of your functions has lovely comments; thank you.
                Recommend you turn the one-sentence comments into docstrings.



                The add_subtitles() function is maybe slightly long,
                and could be broken out into one or two helpers.
                The arg list is on the long side.
                Width + height could trivially be collapsed into size,
                but I wonder if some of the other attributes, like colour,
                might sensibly be defaulted from an object that has an add_subtitles() method.



                 if max_length_subtitles < sub_size[0]:
                max_length_subtitles = sub_size[0]


                A more pythonic way to express this would be to
                construct a list of font sizes, and then
                assign max( ... ) of those sizes.



                Similarly, please assign sub_bg_right as max of two numbers.



                It feels like much of this logic could sensibly be encapsulated
                within a sub_bg object.



                 draw.text(..., font = font)


                PEP-8 asks for spaces around = assignment, but no spaces around = keyword args: font=font.



                Nice comments on make_video().
                Again, it takes quite a few args.
                It feels like a subtitle object could encapsulate several of them.
                These args are trouble:



                 description_intro = [''],
                file_encoding = 'utf-8',
                image_extensions = ['jpg', 'png'],
                audio_extensions = ['mp3', 'wav'],


                Well, file_encoding is fine, just lose the extra blanks around = equals.
                But the lists are trouble.
                Now, I know you're not mutating them.
                But it's a gotcha, evaluating and binding a mutable list at function definition time.
                Don't get in the habit of doing that.
                Make it default to an immutable sequence, such as a (tuple),
                or use the usual idiom:



                def foo(name, extensions=None):
                if extensions is None:
                extensions = ['jpg', 'png']


                The point is to re-evaluate the assignment on each execution of foo(),
                rather than binding an immortal list just once.



                if not os.path.exists(temp_folder):
                os.makedirs(temp_folder)


                Feel free to save one line by specifying , exist_ok=True.



                You could easily break out a few helpers from this function,
                for example the whole audio_filenames loop is naturally a helper function.



                 if not ('artist' in audio_mediainfo):


                That's fine, but testing if 'artist' not in audio_mediainfo: is
                slightly more pythonic.



                 if (artist_override != None): 


                1. No need for ( extra parens ) in a python if.


                2. Please test is identity of the None singleton, rather than equality: if artist_override is not None:. Or, more simply: if artist_override:



                  while (counter_seconds <= limit_audio_length_so_far):


                No ( extra parens ) in a python while, please.



                if video_title == None:


                Please test is None.



                 "It should be under 202-205min (this is a pydub limitation)",
                ...
                "It should be under 4500-5000 characters long (this is a youtube limitation)",


                There are limits, but you're not telling me what they are.
                Put a stake in the ground, say 202 min. and 4500 char. and be done with it.



                title = audio_mediainfo['title'].strip().replace('\', '')


                This is apparently sanitizing a filename, making it safe to open for write.
                That is, you are rejecting Known Bad unicode code points.
                I would be more convinced if you instead used maketrans and translate to
                accept Known Good characters.



                You tend to accommodate long expressions with backwhack continuation characters.
                Consider using ( parens ) instead:



                short_string = 'Hi!'
                much_longer_string = ('This,'
                ' that,'
                ' and the other.')






                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered 12 mins ago









                J_HJ_H

                4,572132




                4,572132



























                    draft saved

                    draft discarded
















































                    Thanks for contributing an answer to Code Review Stack Exchange!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid


                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.

                    Use MathJax to format equations. MathJax reference.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function ()
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191204%2fconverting-mp3-albums-into-mp4-videos-for-youtube%23new-answer', 'question_page');

                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    名間水力發電廠 目录 沿革 設施 鄰近設施 註釋 外部連結 导航菜单23°50′10″N 120°42′41″E / 23.83611°N 120.71139°E / 23.83611; 120.7113923°50′10″N 120°42′41″E / 23.83611°N 120.71139°E / 23.83611; 120.71139計畫概要原始内容臺灣第一座BOT 模式開發的水力發電廠-名間水力電廠名間水力發電廠 水利署首件BOT案原始内容《小檔案》名間電廠 首座BOT水力發電廠原始内容名間電廠BOT - 經濟部水利署中區水資源局

                    Prove that NP is closed under karp reduction?Space(n) not closed under Karp reductions - what about NTime(n)?Class P is closed under rotation?Prove or disprove that $NL$ is closed under polynomial many-one reductions$mathbfNC_2$ is closed under log-space reductionOn Karp reductionwhen can I know if a class (complexity) is closed under reduction (cook/karp)Check if class $PSPACE$ is closed under polyonomially space reductionIs NPSPACE also closed under polynomial-time reduction and under log-space reduction?Prove PSPACE is closed under complement?Prove PSPACE is closed under union?

                    Is my guitar’s action too high? Announcing the arrival of Valued Associate #679: Cesar Manara Planned maintenance scheduled April 23, 2019 at 23:30 UTC (7:30pm US/Eastern)Strings too stiff on a recently purchased acoustic guitar | Cort AD880CEIs the action of my guitar really high?Μy little finger is too weak to play guitarWith guitar, how long should I give my fingers to strengthen / callous?When playing a fret the guitar sounds mutedPlaying (Barre) chords up the guitar neckI think my guitar strings are wound too tight and I can't play barre chordsF barre chord on an SG guitarHow to find to the right strings of a barre chord by feel?High action on higher fret on my steel acoustic guitar