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