Converting MP3 albums into MP4 videos for YouTube

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
6
down vote

favorite
2












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



























    up vote
    6
    down vote

    favorite
    2












    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























      up vote
      6
      down vote

      favorite
      2









      up vote
      6
      down vote

      favorite
      2






      2





      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













      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












      share|improve this question




      share|improve this question








      edited Apr 27 at 2:33









      Jamal♦

      30.1k11114225




      30.1k11114225









      asked Apr 3 at 22:09









      Yulia V

      262210




      262210

























          active

          oldest

          votes











          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',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          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



































          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes










           

          draft saved


          draft discarded


























           


          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













































































          Popular posts from this blog

          Chat program with C++ and SFML

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          Will my employers contract hold up in court?