Dup Ver Goto 📝

Wav and Image/Loop to Mp4

PT2/media/ffmpeg ffmpeg image loop mp4 does not exist
To
141 lines, 494 words, 4013 chars Page 'WavPlusImgToMp4' does not exist.

The main purpose for this is to quickly produce a video for upload to youtube from a .wav file and a backdrop image.

wavimg2mp4

This requires Pillow (python -m pip install Pillow)

Usage:

wavimg2mp4 <audio file> <image file> <output filename> [output-width output-height]

It takes three (or 5) arguments. The default resolution is 1920x1080. It expands or contracts the image to fill the target resolution, cropping as necessary. It then makes a 60 second loop of that image using ffmpeg, then forms a catlist file consisting of as many file 'loop.mp4' lines as necessary that the end result is just longer than the .wav file. Then it uses ffmpeg to merge these two, cropping the result to the length of the .wav file.

#!/usr/bin/env python
from subprocess import run, PIPE, DEVNULL
from PIL import Image
from glob import glob
import sys
import json
import math

def main():
  args = sys.argv[1:]
  try:
    wavfn, imgfn, ofn, *xs = args
    if len(xs) == 2:
      output_w, output_h = map(int,xs)
    else:
      output_w, output_h = 1920, 1080
  except Exception:
    print(f"{sys.argv[0]} wavfn imgfn ofn [output_w output_h]")
    exit(1)
  imgfn = fillimg(imgfn,"tmp.png",output_w,output_h)
  loopfn = mkloop(imgfn)
  wavdur = getdur(wavfn)
  nreq = math.ceil(wavdur/60)
  with open("catlist","wt") as f:
    for n in range(nreq):
      print(f"file '{loopfn}'",file=f)
  run(["ffmpeg","-f","concat","-i","catlist","-i",wavfn,"-b:a","160k","-shortest","-c:v","copy",ofn],stdin=DEVNULL)

def getdur(ifn):
  m = run(["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format",ifn],stdin=DEVNULL,stdout=PIPE)
  if m.returncode > 0:
    print(f"#fail")
    exit(1)
  j = m.stdout.decode()
  d = json.loads(j)
  dur = float(d["format"]["duration"])
  return dur

def mkloop(imgfn):
  loopfn = "loop.mp4"
  cmd=["ffmpeg", "-y", "-loop", "1", "-i", imgfn, "-t", "60", "-r", "24", "-pix_fmt", "yuv420p", "-vf", "scale=1920:1080", loopfn]
  run(cmd,stdin=DEVNULL)
  return loopfn

def fillimg(ifn,ofn,output_w,output_h):
  im = Image.open(ifn)
  iw,ih = im.size
  tw,th = output_w,output_h
  if tw == iw and th == ih:
    return ifn
  ir = iw/ih
  tr = tw/th
  if ir > tr:
    # input is wider when scaled: scale by th/ih
    sf = th/ih
    ow = int(iw*sf)
    oh = int(ih*sf)
    im2 = im.resize((ow,oh),resample=Image.LANCZOS)
    w2,h2 = im2.size
    x2 = (w2-tw)//2
    im3 = im2.crop((x2,0,tw,th))
  else:
    #  input is higher when scaled (or equal), scale by tw/iw
    sf = tw/iw
    ow = int(iw*sf)
    oh = int(ih*sf)
    im2 = im.resize((ow,oh),resample=Image.LANCZOS)
    w2,h2 = im2.size
    y2 = (h2-th)//2
    im3 = im2.crop((0,y2,tw,th))
  im3.save(ofn)
  return ofn

if __name__ == "__main__":
  main()

Wav+Loop to Mp4

This is as above, but skips the image-to-loop stage. This takes a ready-made loop, repeats it as necessary, and then merges it and the wav. Usage:

wavloop2mp4 <audio file> <loop file> <output filename>
#!/usr/bin/env python
from subprocess import run, PIPE, DEVNULL
from glob import glob
from icecream import ic; ic.configureOutput(includeContext=True)
import sys
import json
import math

def main():
  args = sys.argv[1:]
  try:
    wavfn, loopfn, ofn = args
  except Exception:
    print(f"{sys.argv[0]} wavfn loopfn ofn")
    exit(1)
  loopdur = getdur(loopfn)
  wavdur = getdur(wavfn)
  nreq = math.ceil(wavdur/loopdur)
  with open("catlist","wt") as f:
    for n in range(nreq):
      print(f"file '{loopfn}'",file=f)
  run(["ffmpeg","-n","-f","concat","-i","catlist","-i",wavfn,"-b:a","160k","-shortest","-c:v","copy",ofn],stdin=DEVNULL)
  print(ofn)

def getdur(ifn):
  m = run(["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format",ifn],stdin=DEVNULL,stdout=PIPE)
  if m.returncode > 0:
    print(f"#fail")
    exit(1)
  j = m.stdout.decode()
  d = json.loads(j)
  dur = float(d["format"]["duration"])
  return dur

if __name__ == "__main__":
  main()