HMS

Home Media Server for Roku Players
git clone https://www.brianlane.com/git/HMS
Log | Files | Refs | README | LICENSE

makebif.py (5494B)


      1 #!/bin/env python
      2 """
      3 Create .bif files for Roku video streaming
      4 Copyright 2009-2013 by Brian C. Lane <bcl@brianlane.com>
      5 All Rights Reserved
      6 
      7 
      8 makebif.py --help for arguments
      9 
     10 Requires ffmpeg to be in the path
     11 
     12 NOTE: The jpg image sizes are set to the values posted by bbefilms in the Roku
     13       development forums. They may or may not be correct for your video aspect ratio.
     14       They don't look right for me when I set the video height to 480
     15 """
     16 import os
     17 import sys
     18 import tempfile
     19 from subprocess import Popen, PIPE
     20 import struct
     21 import array
     22 import shutil
     23 from optparse import OptionParser
     24 
     25 # for mode 0, 1, 2, 3
     26 videoSizes = [(240,180), (320,240), (240,136), (320,180)]
     27 
     28 # Extension to add to the file for mode 0, 1, 2, 3
     29 modeExtension = ['SD', 'HD', 'SD', 'HD']
     30 
     31 
     32 
     33 def getMP4Info(filename):
     34     """
     35     Get mp4 info about the video
     36     """
     37     details = { 'type':"", 'length':0, 'bitrate':1500, 'format':"", 'size':""}
     38     cmd = ["mp4info", filename]
     39     p = Popen( cmd, shell=True, stdout=PIPE, stderr=PIPE, stdin=PIPE )
     40     (stdout, stderr) = p.communicate()
     41     # Parse the results
     42     for line in stdout.split('\n'):
     43         fields = line.split(None, 2)
     44         try:
     45             if fields[1] == 'video':
     46                 # parse the video info
     47                 # MPEG-4 Simple @ L3, 5706.117 secs, 897 kbps, 712x480 @ 23.9760 24 fps
     48                 videoFields = fields[2].split(',')
     49                 details['type'] = videoFields[0]
     50                 details['length'] = float(videoFields[1].split()[0])
     51                 details['bitrate'] = float(videoFields[2].split()[0])
     52                 details['format'] = videoFields[3]
     53                 details['size'] = videoFields[3].split('@')[0].strip()
     54         except:
     55             pass
     56 
     57     return details
     58 
     59 
     60 def extractImages( videoFile, directory, interval, mode=0, offset=0 ):
     61     """
     62     Extract images from the video at 'interval' seconds
     63 
     64     @param mode 0=SD 4:3 1=HD 4:3 2=SD 16:9 3=HD 16:9
     65     @param directory Directory to write images into
     66     @param interval interval to extract images at, in seconds
     67     @param offset offset to first image, in seconds
     68     """
     69     size = "x".join([str(i) for i in videoSizes[mode]])
     70     cmd = ["ffmpeg", "-i", videoFile, "-ss", "%d" % offset,
     71            "-r", "%0.2f" % (1.00/interval), "-s", size, "%s/%%08d.jpg" % directory]
     72     print cmd
     73     p = Popen( cmd, stdout=PIPE, stdin=PIPE)
     74     (stdout, stderr) = p.communicate()
     75     print stderr
     76 
     77 
     78 def makeBIF( filename, directory, interval ):
     79     """
     80     Build a .bif file for the Roku Player Tricks Mode
     81 
     82     @param filename name of .bif file to create
     83     @param directory Directory of image files 00000001.jpg
     84     @param interval Time, in seconds, between the images
     85     """
     86     magic = [0x89,0x42,0x49,0x46,0x0d,0x0a,0x1a,0x0a]
     87     version = 0
     88 
     89     files = os.listdir("%s" % (directory))
     90     images = []
     91     for image in files:
     92         if image[-4:] == '.jpg':
     93             images.append(image)
     94     images.sort()
     95     images = images[1:]
     96 
     97     f = open(filename, "wb")
     98     array.array('B', magic).tofile(f)
     99     f.write(struct.pack("<I1", version))
    100     f.write(struct.pack("<I1", len(images)))
    101     f.write(struct.pack("<I1", 1000 * interval))
    102     array.array('B', [0x00 for x in xrange(20,64)]).tofile(f)
    103 
    104     bifTableSize = 8 + (8 * len(images))
    105     imageIndex = 64 + bifTableSize
    106     timestamp = 0
    107 
    108     # Get the length of each image
    109     for image in images:
    110         statinfo = os.stat("%s/%s" % (directory, image))
    111         f.write(struct.pack("<I1", timestamp))
    112         f.write(struct.pack("<I1", imageIndex))
    113 
    114         timestamp += 1
    115         imageIndex += statinfo.st_size
    116 
    117     f.write(struct.pack("<I1", 0xffffffff))
    118     f.write(struct.pack("<I1", imageIndex))
    119 
    120     # Now copy the images
    121     for image in images:
    122         data = open("%s/%s" % (directory, image), "rb").read()
    123         f.write(data)
    124 
    125     f.close()
    126 
    127 
    128 def main():
    129     """
    130     Extract jpg images from the video and create a .bif file
    131     """
    132     parser = OptionParser()
    133     parser.add_option(  "-m", "--mode", dest="mode", type='int', default=0,
    134                         help="(0=SD) 4:3 1=HD 4:3 2=SD 16:9 3=HD 16:9")
    135     parser.add_option(  "-i", "--interval", dest="interval", type='int', default=10,
    136                         help="Interval between images in seconds (default is 10)")
    137     parser.add_option(  "-o", "--offset", dest="offset", type='int', default=0,
    138                         help="Offset to first image in seconds (default is 7)")
    139 
    140     (options, args) = parser.parse_args()
    141 
    142     # Get the video file to operate on
    143     videoFile = args[0]
    144     print "Creating .BIF file for %s" % (videoFile)
    145 
    146     # This may be useful for determining the video format
    147     # Get info about the video file
    148     videoInfo = getMP4Info(videoFile)
    149     if videoInfo["size"]: 
    150         size = videoInfo["size"].split("x")
    151         aspectRatio = float(size[0]) / float(size[1])
    152         width, height = videoSizes[options.mode]
    153         height = int(width / aspectRatio)
    154         videoSizes[options.mode] = (width, height)
    155 
    156     tmpDirectory = tempfile.mkdtemp()
    157 
    158     # Extract jpg images from the video file
    159     extractImages( videoFile, tmpDirectory, options.interval, options.mode, options.offset )
    160 
    161     bifFile = "%s-%s.bif" % (os.path.basename(videoFile).rsplit('.',1)[0], modeExtension[options.mode])
    162 
    163     # Create the BIF file
    164     makeBIF( bifFile, tmpDirectory, options.interval )
    165 
    166     # Clean up the temporary directory
    167     shutil.rmtree(tmpDirectory)
    168 
    169 
    170 if __name__ == '__main__':
    171     main()
    172