Skip to content

Instantly share code, notes, and snippets.

@Le09
Last active April 28, 2024 07:53
Show Gist options
  • Save Le09/ed2559f2ae0dd58bbbaa55067a2eb5d6 to your computer and use it in GitHub Desktop.
Save Le09/ed2559f2ae0dd58bbbaa55067a2eb5d6 to your computer and use it in GitHub Desktop.

pop2piano to Youtube pipeline

This is a set of scripts to automate the upload of song covers made by pop2piano directly to YouTube; an automatic pipeline, if you will.

http://sweetcocoa.github.io/pop2piano_samples

We start in one folder with all the songs we want. The pipeline works by having the same filename, except for the extension, for the different parts (image, song, video, ...). So for example, we start with artist - song name.mp3, generate artist - song name.png with Stable Diffusion, and so on.

For each project (pop2piano, Stable diffusion, ...) you are invited to go the linked project page for full installation instructions which are outside of the scope of this document. It only provides the glue to make all of them work together.

This pipeline can easily be adapted at each step, so make it your own!

Requirements

This has been tested on Linux, but it would probably work on Windows with WSL or macOS. You need python (and ideally the ability to manage some virtual environments), ffmpeg, imagemagick, Stable Diffusion if you want to generate images. Pop2piano runs decently on CPU, SD needs a GPU.

Generate the song piano cover:

pop2piano -c 2 -o outputs .
cd outputs
mkdir mixes
mv *.wav mixes
perl-rename  's/.dpipqxiy.composer2//' *
for f in *.mid; do timidity "$f" -Ow -o "${f%.*}".wav; done; 

To run pop2piano in local, I've made a forked branch: https://github.com/Le09/pop2piano/tree/install

To generate the wav from the midi, refer to: https://wiki.archlinux.org/title/Timidity++

I've used the Arachno soundfont.

Generate the links to the original tracks

This step is manual! After generating all url files, I manually write the Youtube link.

for f in *.wav; do echo "" > "${f%.*}".url; done; 

After this step our folder should contain song_name.url for each song_name.wav.

Generate the image covers

Automation of this step requires a Python script on top of the command line. It gives the option to input an alternate prompt for each song (some prompts ended up almost NSFW so this was needed). Because the generation of each image batch is fairly long, it makes a bell song at each iteration to help knowing when to input the name.

For the installation, refer to: https://github.com/AUTOMATIC1111/stable-diffusion-webui

Automation guide: https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/API

You need to launch it with the API argument, on Linux ./webui.sh --api.

import os
import sys
import base64
import requests

root_url = "http://127.0.0.1:7860/sdapi/v1/"

default_payload = {
  "batch_size": 1,
  "n_iter": 1,
  "steps": 20,
  "cfg_scale": 7,
  "width": 512,
  "height": 512,
  "restore_faces": True,
}

def get_images(file_name, prompt, **kwargs):
    if not os.path.isdir(file_name):
        os.mkdir(file_name)
    if prompt != file_name:
        with open(os.path.join(file_name, prompt), 'w') as f:
            f.write("\n")
    data = dict(default_payload, prompt=prompt, **kwargs)
    r = requests.post(root_url + "txt2img", json=data)
    imgs_encoded = r.json()["images"] 
    for i, encoded in enumerate(imgs_encoded):
        img_name = file_name + "_" + str(i) + ".png"
        img_path = os.path.join(file_name, img_name)
        with open(img_path, 'wb') as f:
            f.write(base64.b64decode(encoded))

if __name__ == '__main__':
    file_path = sys.argv[1]
    file_name = os.path.splitext(file_path)[0]
    prompt = input("Put alternate prompt for cover images (nothing to use the title):\n")
    if not prompt:
        prompt = file_name
    get_images(file_name, prompt)

Now we just need to loop over each song and build the composite:

mkdir -p imgs
for f in *.wav; do
    ffolder="${f%.*}"
    echo $ffolder
    mkdir -p "$ffolder"
    python sd.py "$f"
    montage -mode Concatenate -tile 2x "$ffolder"/*.png imgs/"$ffolder".png
    paplay /usr/share/sounds/freedesktop/stereo/complete.oga
done

At this step our folder should contain imgs/song_name.png for each song_name.wav. Each image in imgs should be 1024x1024 (since it is made of four 512 pixels square images).

Then generate manually the common left hand side of the video image. It is called pop2pianothumb.png file, and should be 896x1080 to end up with a 1920x1080 image. This could be optional as long at after this step each song_name.wav is paired with a song_name.png (so in the same folder, not in the imgs subfolder anymore).

mkdir -p cover_imgs
for f in imgs/*.png; do
    montage -mode Concatenate -tile 2 -gravity center pop2pianothumb.png "$f" -background black cover_"$f"
done
mv cover_imgs/* .
rm cover_imgs

More information on ImageMagick here: https://wiki.archlinux.org/title/ImageMagick

Generate the videos

At this step our folder should contain song_name.png and song_name.wav. Generating the videos is thus a simple ffmpeg step.

for f in *.wav; do 
    ffmpeg -loop 1 -i "${f%.*}".png -i "$f" -c:v libx264 -tune stillimage -c:a aac -b:a 192k -pix_fmt yuv420p -shortest "${f%.*}".mp4
done

More information on FFmpeg here: https://wiki.archlinux.org/title/FFmpeg

Upload the files:

At this step our folder should contain song_name.mp4 and song_name.url. You just need to complete the description, input your YouTube secrets and the file paths to the video files.

This upload is only barely changed from the example: https://github.com/pillargg/youtube-upload

Refer to this for a guide on how get access to the YouTube API, navigate the Google console to create the secrets.

import os
from youtube_upload.client import YoutubeUploader

secret_path = "/path/to/client_secret.ytupload_desktop.googleusercontent.json"
root_folder = "/path/to/Music/to_cover"

description = """Link to the original song: %s
The rest of your video description.
"""


def upload_video(root, filepaths):
    uploader = YoutubeUploader(secrets_file_path=secret_path)
    uploader.authenticate()

    for file_name in filepaths:
        file_path = os.path.join(root, file_name)
        name = file_name.replace(".mp4", "")
        url = open(file_path.replace("mp4", "url"), "r").read().strip()
        title = f"{name} (pop2piano AI cover)"
        options = {
            "title" : title,
            "description" : description % url,
            "tags" : ["ai", "pop2piano", "piano cover"],
            "categoryId" : "10",
            "privacyStatus" : "private", # Video privacy. Can either be "public", "private", or "unlisted"
            "kids" : False,
        }
        uploader.upload(file_path, options)

    uploader.close()

if __name__ == '__main__':
    file_paths = []
    for file_path in os.listdir(root_folder):
        if file_path.endswith(".mp4"):
            file_paths.append(file_path)
    upload_video(root_folder, file_paths)

After a test you can directly make the videos public :-)

@itsbrex
Copy link

itsbrex commented Apr 13, 2024

Thx for sharing this. Very cool! 🙏

@Le09
Copy link
Author

Le09 commented Apr 28, 2024

Thank you! Don't hesitate to share a link if you do something with it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment