User:Jasper van Loenen/Prototyping/svgMovie

From XPUB & Lens-Based wiki

Converting a YouTube video to an animated svg file.

Example (this svg seems to work only in Chrome... (not Firefox))

import Image, os, sys

#set some initial values which can be overwritten using arguments
frameStep = 10 #the image will animate from frame to frame. To decrease load/filesize it will use every Nth frame
gridSize = 20.0 #how many blocks
tweenSpeed = (1.0 / 24.0) * frameStep #it calculates the relative FPS

# Download the video given as the first argument, if no argument is found: die
if len(sys.argv)<=1:
    print "Error: this script takes a single YouTube URL as argument!"
    exit()
else:
    if len(sys.argv) > 2:
        gridSize = sys.argv[2]
    if len(sys.argv) > 3:
        frameStep = sys.argv[3]

#create a temp folder to save the video and such (clear if already exists)
os.system("rm -r tmp")
os.system("mkdir tmp")
os.system("youtube-dl -quiet -o tmp/video.%\(ext\)s "+sys.argv[1])

# Check if the file was downloaded and get a path to it. If no file found, clear temp folder and die
files = os.listdir("tmp/")
if len(files)==0:
    if os.listdir("tmp/"):
        os.system("rm -r tmp")
    print "Error: the tmp folder is empty."
    print "I guess something went wrong while downloading the video :("
    exit()
else:
    totalFrames = len(files)

vidPath = "tmp/"+files[0]
# Split the audio and save it seperately for possible later use
os.system("ffmpeg -i "+vidPath+" tmp/audio.wav")
# Render the frames to images
os.system("ffmpeg -i "+vidPath+" -f image2 tmp/originalFrame-%06d.png")
os.system("convert -sample 10% -sample 1000% tmp/originalFrame-%06d.png tmp/newFrame-%06d.png")

class GridBlock:

    def __init__(self, _x, _y, _width, _height, _number) :
        self.x = _x
        self.y = _y
        self.w = _width
        self.h = _height
        self.number = _number
        self.firstRun = True
        self.blockFrame = 0;

        self.prevR, self.prevG, self.prevB = 0, 0, 0
        self.blockOutput = "<rect id=\"block"+str(self.number)+"\" x=\""+str(self.x)+"\" y=\""+str(self.y)+"\" width=\""+str(self.w)+"\" height=\""+str(self.h)+"\" style=\"fill:black\">\n"
        return

    def updateColor(self, im):
        source = im.split()
        totalR, totalG, totalB = 0, 0, 0
        totalChecked = 0
        totalR, totalG, totalB = 0, 0, 0
        for xPos in range(self.x, int(self.x+self.w)):
            for yPos in range(self.y, int(self.y+self.h)):
                pix = im.getpixel((xPos,yPos))
                totalR += pix[0]
                totalG += pix[1]
                totalB += pix[2]
                totalChecked+=1
      
        currR = totalR / totalChecked
        currG = totalG / totalChecked
        currB = totalB / totalChecked

        if self.firstRun==True:
            self.prevR = currR
            self.prevG = currG
            self.prevB = currB
            self.firstRun = False

        self.blockOutput += "\t<animateColor attributeName=\"fill\" attributeType=\"CSS\"\n\tfrom=\"rgb("+str(self.prevR)+", "+str(self.prevG)+", "+str(self.prevB)+")\" to=\"rgb("+str(currR)+", "+str(currG)+", "+str(currB)+")\" begin=\""+str(self.blockFrame*tweenSpeed)+"s\" dur=\""+str(tweenSpeed)+"s\" fill=\"freeze\"/>\n"
        self.prevR = currR
        self.prevG = currG
        self.prevB = currB
        self.blockFrame+=1
        return

    def endOutput(self):
        self.blockOutput += "</rect>\n\n"
        return

gridWidth = 0
gridHeight = 0
blocksList = []
formattedCurrFrame  = ""
W, H = 0, 1

frameImg = Image.open("tmp/originalFrame-000001.png")
if gridSize > frameImg.size[W]:
    gridSize = frameImg.size[W]
elif gridSize > frameImg.size[H]:
    gridSize = frameImg.size[H]
elif gridSize < 1:
    gridSize = 1

gridWidth = frameImg.size[W] / gridSize;
gridHeight = frameImg.size[H] / gridSize;
index = 0;

for y in xrange(0, frameImg.size[H], int(gridHeight)):
    for x in xrange(0, frameImg.size[W], int(gridWidth)):
        if index < gridSize*gridSize:
            blocksList.append(GridBlock(x, y, gridWidth, gridHeight, index))
            index+=1

#There must be a more efficient way to do the following, didn't look it up yet
for currFrame in xrange(1, totalFrames+1, frameStep):
    if currFrame < 10:
        formattedCurrFrame = "00000" + str(currFrame)
    elif currFrame < 100:
        formattedCurrFrame = "0000" + str(currFrame)
    elif currFrame < 1000:
        formattedCurrFrame = "000" + str(currFrame)
    elif currFrame < 10000:
        formattedCurrFrame = "00" + str(currFrame)
    elif currFrame < 100000:
        formattedCurrFrame = "0" + str(currFrame)
    else:
        formattedCurrFrame = ""+str(currFrame)
    
    frameImg = Image.open("tmp/originalFrame-"+formattedCurrFrame+".png")
    frameImg.load()
    index = 0
    for i in xrange(0, len(blocksList)):
        blocksList[i].updateColor(frameImg) 

svgOut = "<?xml version=\"1.0\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">";
svgOut += "\n\n<svg width=\""+str(frameImg.size[0])+"\" height=\""+str(frameImg.size[1])+"\" version=\"1.1\"\nxmlns=\"http://www.w3.org/2000/svg\">\n\n";

for i in xrange(0, len(blocksList)):
    blocksList[i].endOutput()
    svgOut += blocksList[i].blockOutput

svgOut += "</svg>"

# Output the results so they can be piped into another script
print "Saving svg file..."
outputFile = open("result.svg", "w")
outputFile.write(svgOut)
outputFIle.close()

# Clean up by deleting the downloaded video and generated images
os.system("rm -r tmp")
print "All done!"