Author Topic: Video Conversion Thingie  (Read 4509 times)

0 Members and 2 Guests are viewing this topic.

Offline Iambian

  • Coder Of Tomorrow
  • LV8 Addict (Next: 1000)
  • ********
  • Posts: 739
  • Rating: +216/-3
  • Cherry Flavoured Nommer of Fishies
    • View Profile
Video Conversion Thingie
« on: August 24, 2016, 07:19:33 pm »
This was largely inspired/copied-wherever-appropriate from this older project where I tried to get this video or one of its variants to play on a calculator in a somewhat efficient manner.

Two days, and many tears over lost notes because apparently if you don't test your backups, you ain't got a backup, and a bit of magic with Python and varying libraries, I recreated what I had except it has a much neater process of making a video that doesn't require any intermediate user interaction or arcane commnad-line invocations.

Spoiler For That Python Script:
Pasting my Python script here since I really don't want to lose it.
Code: [Select]
''' Video converter - To project-specific format
    In:  VIDEO FILE
    Out: A bunch of data files that the project can compile
'''

from PIL import Image,PngImagePlugin,ImageTk,ImageOps
from ffmpy import FFmpeg
import sys,os,subprocess
import Tkinter as tk

# ---------------------------------------------------------------------------
class Application(tk.Frame):
  def __init__(self, master=None):
    tk.Frame.__init__(self, master)
    self.master.title("* Ohhhh yesss!")
    self.master.geometry('200x200')
    self.master.minsize(200,200)
    self.pack()
   
    self.img = ImageTk.BitmapImage(Image.new('1',(96,64),0),foreground='black')
    self.canvas = tk.Canvas(self.master,width=96,height=64)
    self.canvas.place(x=10,y=10,width=100,height=100)
    self.canvas.configure(bg='white',width=96,height=64,state=tk.NORMAL)
    self.imgobj = self.canvas.create_image(1,1,image=self.img,anchor=tk.NW,state=tk.NORMAL)
   
  def updateframe(self,bytearray):
    self.img = ImageTk.BitmapImage(Image.frombytes('1',(96,64),bytearray),foreground='black')
    self.canvas.itemconfig(self.imgobj,image=self.img)
    self.update_idletasks()
    self.update()
print "Starting Program"
root = tk.Tk()
app = Application(root)
app.update_idletasks()
app.update()
# ---------------------------------------------------------------------------
np  = os.path.normpath
cwd = os.getcwd()

af_called = 0
buffers = 0
max_frames = 23
cur_frame  = 0
framebuf = []
compressed_arrays = []
total_len = 0
#-----------------------------------------------------------------------------
def readFile(file):
  a = []
  f = open(file,'rb')
  b = f.read(1)
  while b!=b'':
    a.append(ord(b))
    b = f.read(1)
  f.close()
  return a
 
def writeFile(file,a):
  f = open(file,'wb+')
  f.write(bytearray(a))
  f.close()
       
def addframe(framedata=None):
  global max_frames,cur_frame,framebuf,af_called,compressed_arrays,buffers,total_len
  af_called += 1
  if framedata:
    framedata = ''.join([chr(~ord(c)&0xFF) for c in framedata])
    app.updateframe(framedata)
    framebuf.extend(framedata)
    cur_frame += 1
    if cur_frame >= max_frames:
      framedata = None
  # If framedata was None either on init or set during running, flush buffer.
  if not framedata and framebuf:
    tfn  = np(cwd+'/bin/tempimg')
    tfnc = np(cwd+'/bin/tempimgc')
    writeFile(tfn,framebuf)
    subprocess.call([np(cwd+'/tools/apack.exe'),tfn,tfnc])
    outarray = readFile(tfnc)
    compressed_arrays.append([buffers,len(outarray),outarray])
    total_len += len(outarray)
    buffers += 1
    print "Buffer "+str(buffers)+" flushed at call number "+str(af_called)
    framebuf = []
    cur_frame = 0
   
def dumptofile(fn,arrobj):
  filename = np(cwd+'/dat/'+fn)
  with open(filename,'w') as f:
    curchar = 0
    s = ''
    for b in arrobj[2]:
      if curchar == 0:
        s = '.db '
      s = s+'$%02X' % b
      if curchar == 16:
        f.write(s+"\n")
        s = ''
        curchar = 0
      else:
        s = s+','
        curchar += 1
    if s:
      f.write(s[:len(s)-1]+'\n')
     

def usage():
    print '''Biscuits Video Converter Usage:\n
             python convert.py <input_video_file.ext>\n'''
    return
#-----------------------------------------------------------------------------
'''FFMPEG NOTES:
  -ss timestamp       : Grab video starting at this timestamp
  -c copy             : -c for codec. 'copy' keyword to ensure no reencoding
  -t timestamp        : Duration of encoding
  -vf filtergraph     : Alias for -filter:v (filter for video stream) using
                        the crap in 'filtergraph'.

'''
input_commands  = [
  '-ss 00:00:04.0',
  None,
 
]
output_commands = [
  '-ss 00:00:04.0 -c copy -t 00:01:00.0',
  '-vf scale=-1.64',
]


print "Application starting..."

try:
  infile  = sys.argv[1]
  outfile1 = np(cwd+'/bin/temp1.mp4')
  outfile2 = np(cwd+'/bin/temp2.mp4')
  outfilei = np(cwd+'/bin/img/i%05d.png')
  if not os.path.isfile(infile):
    print "Error: "+str(infile)+" does not exist."
    sys.exit(1)
except:
  print usage()
  sys.exit(1)

 
print ":::: Pass 0" 
FFmpeg(
 inputs  = {infile :'-y'},
 outputs = {outfile1:'-c:v libx264 -crf 0 -preset ultrafast'},
).run()
 

print ":::: Pass 1" 
FFmpeg(
  inputs  = {outfile1:'-y'},
  outputs = {outfile2:'-vf scale=-1:64'},
).run()

print ":::: Pass 2"
FFmpeg(
  inputs  = {outfile2: '-y -ss 00:00:04.0'},
  outputs = {outfile1:'-ss 00:00:04.0 -async 1 -t 00:01:00.0'},
).run()

print ":::: Pass 3"
FFmpeg(
  inputs  = {outfile1:'-y'},
  outputs = {outfile2:'-vf crop=96:64:(in_w-96)/2:0'},
).run()

print ":::: Pass 4"
FFmpeg(
  inputs  = {outfile2:'-y'},
  outputs = {outfilei:'-f image2 -r 30'},
).run()

print ":::: Beginning file conversion"

flist = sorted(os.listdir(np(cwd+'/bin/img')))

for f in flist:
  addframe(Image.open(np(cwd+'/bin/img/'+f)).convert('1').tobytes())
addframe() #flush remaining buffer

#compressed_arrays.append([buffers,len(outarray),outarray])

with open(np(cwd+'/dat/filelist.z80'),'w') as flister:
  flister.write('#define getCurpage(x) x>>16\n')
  for i in compressed_arrays:
    tfn = 'I'+str(i[0]).zfill(3)+'.z80'
    flister.write('.dw '+tfn+' \\.db getCurpage('+tfn+')\n')

with open(np(cwd+'/dat/numfiles.inc'),'w') as nfil:
  nfil.write("NUMBER_OF_FILES .EQU "+str(len(compressed_arrays)))
#Sort by length of outarray, descending
compressed_arrays = sorted(compressed_arrays,key=lambda i:i[1],reverse=True)

slack = -1
curfile = 0
curpage = 0
tslack = 0
maxslack = 16383
with open(np(cwd+'/dat/myinc.z80'),'w') as finc:
  while len(compressed_arrays)>0:
    curpage += 1
    finc.write('defpage('+str(curpage)+') ;Prev page slack:'+str(slack)+'\n')
    slack = maxslack
    i=0
    while i<len(compressed_arrays):
      if compressed_arrays[i][1]>slack:
        i+=1
      else:
        a = compressed_arrays.pop(i)
        tfn = 'I'+str(a[0]).zfill(3)+'.z80'
        slack -= a[1]
        dumptofile(tfn,a)
        finc.write(tfn+': #include "dat/'+tfn+'" ;File length: '+str(a[1])+'\n')
    tslack += slack
    print "Page "+str(curpage).zfill(3)+" with slack "+str(slack).zfill(5)+" emitted."
 

print "Compressed "+str(768*len(flist))+" bytes to "+str(total_len)+' with slack bytes '+str(tslack)
print ":::: Completed."

I'm also total Undertale trash and I wanted to do something that involved it. So I chose one particular video and ran the converter. I think it turned out pretty well. Check it out here:


Attached to the post is the empty source needed to build and the app that resulted from the build.
A Cherry-Flavored Iambian draws near... what do you do? ...

Offline Deep Toaster

  • So much to do, so much time, so little motivation
  • Administrator
  • LV13 Extreme Addict (Next: 9001)
  • *************
  • Posts: 8217
  • Rating: +758/-15
    • View Profile
    • ClrHome
Re: Video Conversion Thingie
« Reply #1 on: August 25, 2016, 06:46:05 am »
Waoh that's really neat o.o Does it work with arbitrary videos?

I haven't looked much at your code but how do you store the video on the calculator? is it full frames?

I've been meaning to try making a GIF-style video player for the calculator (updating only the smallest rectangle in each frame that covers all the changes in the frame)—should still be pretty fast to render, but I don't know if that saves much memory.




Offline Sorunome

  • Fox Fox Fox Fox Fox Fox Fox!
  • Support Staff
  • LV13 Extreme Addict (Next: 9001)
  • *************
  • Posts: 7920
  • Rating: +374/-13
  • Derpy Hooves
    • View Profile
    • My website! (You might lose the game)
Re: Video Conversion Thingie
« Reply #2 on: August 25, 2016, 07:19:27 am »
This is looking awesome!
About your python script (it probably uses the file reading/writing functions i made you some time ago, right? :P) Anyhow i'd recommend replacing this:
Code: [Select]
def readFile(file):
  a = []
  f = open(file,'rb')
  b = f.read(1)
  while b!=b'':
    a.append(ord(b))
    b = f.read(1)
  f.close()
  return a
 
def writeFile(file,a):
  f = open(file,'wb+')
  f.write(bytearray(a))
  f.close()
with this:
Code: [Select]
def readFile(file):
  with open(file,'rb') as f:
    a = []
    b = f.read(1)
    while b!=b'':
      a.append(ord(b))
      b = f.read(1)
  return a
 
def writeFile(file,a):
  with open(file,'wb+') as f;
    f.write(bytearray(a))
As you can see, there is no need for an f.close() as the with...as statement does that for you, that also guarantees that the filehandler gets closed in the case of an exception in that block

THE GAME
Also, check out my website
If OmnomIRC is screwed up, blame me!
Click here to give me an internet!

Offline Deep Toaster

  • So much to do, so much time, so little motivation
  • Administrator
  • LV13 Extreme Addict (Next: 9001)
  • *************
  • Posts: 8217
  • Rating: +758/-15
    • View Profile
    • ClrHome
Re: Video Conversion Thingie
« Reply #3 on: August 25, 2016, 07:23:28 am »
with++

After learning Haskell I feel like everything ought to be as functional as possible...




Offline TIfanx1999

  • ಠ_ಠ ( ͡° ͜ʖ ͡°)
  • CoT Emeritus
  • LV13 Extreme Addict (Next: 9001)
  • *
  • Posts: 6173
  • Rating: +191/-9
    • View Profile
Re: Video Conversion Thingie
« Reply #4 on: September 03, 2016, 09:56:47 pm »
Real cutscenes in Escheron next plox. :P Seriously though, this looks pretty sweet. ^^