Using BIF files

Hello!

How can i use my “.bif” file, to show the fast forward/backward list of thumbnails, if i disabled trick play?
e.g.: “m.video.enableTrickPlay = false”

Thank you!

  • Gabriel

Can you elaborate on your request, please? You’ve created your .bif file but don’t want to implement trickplay? In what way are you hoping to display your images?

Hi Tom!

Sorry for sharing not enough information. I want to know if is possible to use from trick play, only the “bif images feature”. I’ve created a custom player, with a custom seekbar, and other views above the video as title, time spent, etc. But i can’t find how to decode a “.bif” file into a RowList or to create a custom TrickPlayBar node.

Thanks!

  • Gabriel

Thanks for clarifying, Gabriel. Unfortunately, you can not create a custom TrickPlayBar node. There’s no way to extract the images from a .bif file

Actually there is, but it is a lot of work.

Something like this works in SDK 3.

	episode.HDBifUrl="http://server/filename.bif"
	
	m.ThumbsExist=TRUE 'Default to true
	UT=CreateObject("roUrlTransfer")
	UT.SetPort(port)
	UT.SetUrl(episode.HDBifUrl)
	
	result=UT.GetToFile("tmp:/biffile.bif") 'download bif file
	If result<>200
		m.ThumbsExist=FALSE
		Print"BIF File not found!"
		m.TimestampMultiplier=10000
		m.TotalFrameCount=INT(episode.runtime/10)+1
	End If
	
	If m.ThumbsExist=TRUE
		m.dataset=CreateObject("roByteArray")
		m.headerset=CreateObject("roAssociativeArray")
		m.dataset.ReadFile("tmp:/biffile.bif")
		m.BifVersion=-1
		If m.dataset.Count()>0
			'verify filetype
			'0-7=magic number unique file identifier=offset
			If m.dataset[0]=&h89 And m.dataset[1]=&h42 And m.dataset[2]=&h49 And m.dataset[3]=&h46 And m.dataset[4]=&h0d And m.dataset[5]=&h0a And m.dataset[6]=&h1a And m.dataset[7]=&h0a
				Print "valid BIF File Identifier"
				'8-11=bif file format version number
				If m.dataset[8]=0 And m.dataset[9]=0 And m.dataset[10]=0 And m.dataset[11]=0
					Print"Using Version 0 descrambler"
					m.BifVersion=0
				Else
					Print"unknown version format"
				End If
			Else
				Print"Invalid BIF File"
			End If
		Else
			Print"Empty File or File doesn't exist"
		End If
		
		If m.BifVersion=0
			'12-15=total number of bif file images
			m.TotalFrameCount=(m.dataset[12]+m.dataset[13]*256+m.dataset[14]*256*256+m.dataset[15]*256*256*256)
			Print "Total Frames= "+itostr(m.TotalFrameCount)
			
			'16-19=timestamp multiplier
			m.TimestampMultiplier=(m.dataset[16]+m.dataset[17]*256+m.dataset[18]*256*256+m.dataset[19]*256*256*256)
			If m.TimestampMultiplier=0 Then m.TimestampMultiplier=1000 'if 0, BIF specification treats it as 1000ms
			Print"Timestamp Multiplier= "+itostr(m.TimestampMultiplier)+" ms"
			
			'20-63=reserved for future expansion - all 00 bytes
			
			'frame index table repeats until all frames are accounted for
			'64-67=frame 0 index timestamp
			'68-71=frame 0 absolute offset of frame
			'last index frame
			'0xffffffff
			'followed by last byte of data +1	
			
			m.Offset=64 'initialize byte number counter
			m.TimeIndexTable=[]
			m.OffsetTable=[]
			For X=0 To m.TotalFrameCount-1 'run through entire index table
				T1=(m.dataset[m.Offset]+m.dataset[m.Offset+1]*256+m.dataset[m.OffSet+2]*256*256+m.dataset[m.OffSet+3]*256*256*256)
				T2=(m.dataset[m.Offset+4]+m.dataset[m.Offset+5]*256+m.dataset[m.Offset+6]*256*256+m.dataset[m.Offset+7]*256*256*256)
				m.TimeIndexTable.Push(T1)
				m.OffsetTable.Push(T2)
				m.Offset=m.Offset+8
			Next
			Print "Start of Last Frame= "+itostr(T2)
			m.EndOfFileMarker=(m.dataset[m.Offset+4]+m.dataset[m.Offset+5]*256+m.dataset[m.Offset+6]*256*256+m.dataset[m.Offset+7]*256*256*256)
			Print "Last byte of file= "+itostr(m.EndOfFileMarker)
			
			'DATA Section follows with JPG files
			m.Frames=[] 'create fromes array for image0/1/2 display - 3 frames possible at 160 width for 480 pixels
					'5 frames will not fit without making thumbnails in bif file smaller
		End If
	End If
	

Once you have the frames array then you can draw them as a bitmap, canvas, screen, or anything you like – but as those features are going to be stripped out of the roku, I’m not sure how you would display them with scenegraph.

I had to do this for a 3D channel - so it was years ago - here’s the draw code - note, that everything is doubled horizontally as it’s intended for a 3D TV. So you’d need to edit this to get it back to single screen/2D.

Sub DrawPauseScreen(Episode As Object)
	m.canvas.Clear(&h000000FF)
	If m.ThumbsExist=TRUE
		If INT(m.position/(m.TimestampMultiplier/1000))>0
			m.Frames[0]=INT(m.position/(m.TimestampMultiplier/1000))-1
		Else
			m.Frames[0]=-1
		End If
		m.Frames[1]=INT(m.position/(m.TimestampMultiplier/1000))
		If INT(m.position/(m.TimestampMultiplier/1000))<m.TotalFrameCount-1
			m.Frames[2]=m.Frames[1]+1
		Else
			m.Frames[2]=-1
		End If
		
		FrameBorderSides=CreateObject("roBitmap","pkg:/assets/square.png")
		FrameMask=CreateObject("roBitmap",{width:180,height:200,alphaenable:false})
		FrameMask.Clear(&hFFFFFF00)
		
		'DRAW SCREEN
		If m.Frames[0]>-1
			m.dataset.WriteFile("tmp:/frame"+itostr(m.Frames[0])+".png",m.OffsetTable[m.Frames[0]],m.OffsetTable[m.Frames[0]+1]-m.OffsetTable[m.Frames[0]])
			FBMP0=CreateObject("roBitmap","tmp:/frame"+itostr(m.Frames[0])+".png")
			SourceBMP0Left=CreateObject("roRegion",FBMP0,0,0,160,180)
			SourceBMP0Right=CreateObject("roRegion",FBMP0,160,0,160,180)
			m.canvas.DrawObject(26,270,SourceBMP0Left)
			m.canvas.DrawObject(16,260,FrameBorderSides)
			
			m.canvas.DrawObject(666,270,SourceBMP0Right)
			m.canvas.DrawObject(656,260,FrameBorderSides)
		Else
			m.canvas.DrawObject(16,260,FrameMask)
			m.canvas.DrawObject(656,260,FrameMask)
		End If
		If m.Frames[2]>-1 
			If m.Frames[2]<m.TotalFrameCount-1
				m.dataset.WriteFile("tmp:/frame"+itostr(m.Frames[1])+".png",m.OffsetTable[m.Frames[1]],m.OffsetTable[m.Frames[1]+1]-m.OffsetTable[m.Frames[1]])
				FBMP1=CreateObject("roBitmap","tmp:/frame"+itostr(m.Frames[1])+".png")
				SourceBMP1Left=CreateObject("roRegion",FBMP1,0,0,160,180)
				SourceBMP1Right=CreateObject("roRegion",FBMP1,160,0,160,180)
				m.canvas.DrawScaledObject(218,252,1.25,1.25,SourceBMP1Left)
				m.canvas.DrawScaledObject(206,240,1.24,1.24,FrameBorderSides)
				
				m.canvas.DrawScaledObject(858,252,1.25,1.25,SourceBMP1Right)
				m.canvas.DrawScaledObject(846,240,1.24,1.24,FrameBorderSides)
				
				m.dataset.WriteFile("tmp:/frame"+itostr(m.Frames[2])+".png",m.OffsetTable[m.Frames[2]],m.OffsetTable[m.Frames[2]+1]-m.OffsetTable[m.Frames[2]])
				FBMP2=CreateObject("roBitmap","tmp:/frame"+itostr(m.Frames[2])+".png")
				SourceBMP2Left=CreateObject("roRegion",FBMP2,0,0,160,180)
				SourceBMP2Right=CreateObject("roRegion",FBMP2,160,0,160,180)
				m.canvas.DrawObject(450,270,SourceBMP2Left)
				m.canvas.DrawObject(440,260,FrameBorderSides)
				
				m.canvas.DrawObject(1090,270,SourceBMP2Right)
				m.canvas.DrawObject(1080,260,FrameBorderSides)
			Else
				m.dataset.WriteFile("tmp:/frame"+itostr(m.Frames[1])+".png",m.OffsetTable[m.Frames[1]],m.OffsetTable[m.Frames[1]+1]-m.OffsetTable[m.Frames[1]])
				FBMP1=CreateObject("roBitmap","tmp:/frame"+itostr(m.Frames[1])+".png")
				SourceBMP1Left=CreateObject("roRegion",FBMP1,0,0,160,180)
				SourceBMP1Right=CreateObject("roRegion",FBMP1,160,0,160,180)
				m.canvas.DrawScaledObject(218,252,1.24,1.24,SourceBMP1Left)
				m.canvas.DrawScaledObject(206,240,1.24,1.24,FrameBorderSides)
				
				m.canvas.DrawScaledObject(858,252,1.24,1.24,SourceBMP1Right)
				m.canvas.DrawScaledObject(846,240,1.24,1.24,FrameBorderSides)
				
				m.dataset.WriteFile("tmp:/frame"+itostr(m.Frames[2])+".png",m.OffsetTable[m.Frames[2]],m.EndOfFileMarker+1-m.OffsetTable[m.Frames[2]])
				FBMP2=CreateObject("roBitmap","tmp:/frame"+itostr(m.Frames[2])+".png")
				SourceBMP2Left=CreateObject("roRegion",FBMP2,0,0,160,180)
				SourceBMP2Right=CreateObject("roRegion",FBMP2,160,0,160,180)
				m.canvas.DrawObject(450,270,SourceBMP2Left)
				m.canvas.DrawObject(440,260,FrameBorderSides)
				
				m.canvas.DrawObject(1090,270,SourceBMP2Right)
				m.canvas.DrawObject(1080,260,FrameBorderSides)
			End If
		Else
			m.canvas.DrawObject(440,260,FrameMask)
			m.canvas.DrawObject(1080,260,FrameMask)
			
			m.dataset.WriteFile("tmp:/frame"+itostr(m.Frames[1])+".png",m.OffsetTable[m.Frames[1]],m.EndOfFileMarker+1-m.OffsetTable[m.Frames[1]])
			FBMP1=CreateObject("roBitmap","tmp:/frame"+itostr(m.Frames[1])+".png")
			SourceBMP1Left=CreateObject("roRegion",FBMP1,0,0,160,180)
			SourceBMP1Right=CreateObject("roRegion",FBMP1,160,0,160,180)
			m.canvas.DrawScaledObject(218,252,1.25,1.25,SourceBMP1Left)
			m.canvas.DrawScaledObject(206,240,1.24,1.24,FrameBorderSides)
			
			m.canvas.DrawScaledObject(858,252,1.25,1.25,SourceBMP1Right)
			m.canvas.DrawScaledObject(846,240,1.24,1.24,FrameBorderSides)
		End If
		
	End If
	'END OF THUMBS IF THEY EXISTED - now draw progress bar and button indicator graphic/speed
	ProgressBar=CreateObject("roBitmap","pkg:/assets/playerprogressbar.png")
	ProgressMarker=CreateObject("roBitmap","pkg:/assets/playerprogressmarker.png")
	ProgressMarkerArrow=CreateObject("roBitmap","pkg:/assets/playerprogressmarkerbottom.png")  '************************** needs to be utilized - X is the same as progressmarker, y is +62
	PauseIcon=CreateObject("roBitmap","pkg:/assets/playerpaused.png")
	FSlowIcon=CreateObject("roBitmap","pkg:/assets/playerfslow.png")
	RSlowIcon=CreateObject("roBitmap","pkg:/assets/playerrslow.png")
	
	FFastIcon1=CreateObject("roBitmap","pkg:/assets/playerffast1.png")
	FFastIcon2=CreateObject("roBitmap","pkg:/assets/playerffast2.png")
	FFastIcon3=CreateObject("roBitmap","pkg:/assets/playerffast3.png")
	RFastIcon1=CreateObject("roBitmap","pkg:/assets/playerrfast1.png")
	RFastIcon2=CreateObject("roBitmap","pkg:/assets/playerrfast2.png")
	RFastIcon3=CreateObject("roBitmap","pkg:/assets/playerrfast3.png")
	
	If m.stepspeed=-6
		m.canvas.DrawObject(291,520,RFastIcon3) '509 to center between the top of the progress indicator and the bottom of the center thumbnail frame
		m.canvas.DrawObject(931,520,RFastIcon3) '520 to center between the top of the horizontal progress bar itself and the bottom of the center thumbnail frame
	End If
	If m.stepspeed=-4
		m.canvas.DrawObject(291,520,RFastIcon2) '509 to center between the top of the progress indicator and the bottom of the center thumbnail frame
		m.canvas.DrawObject(931,520,RFastIcon2) '520 to center between the top of the horizontal progress bar itself and the bottom of the center thumbnail frame
	End If
	If m.stepspeed=-2
		m.canvas.DrawObject(291,520,RFastIcon1) '509 to center between the top of the progress indicator and the bottom of the center thumbnail frame
		m.canvas.DrawObject(931,520,RFastIcon1) '520 to center between the top of the horizontal progress bar itself and the bottom of the center thumbnail frame
	End If
	If m.stepspeed=-1
		m.canvas.DrawObject(291,520,RSlowIcon) '509 to center between the top of the progress indicator and the bottom of the center thumbnail frame
		m.canvas.DrawObject(931,520,RSlowIcon) '520 to center between the top of the horizontal progress bar itself and the bottom of the center thumbnail frame
	End If
	If m.stepspeed=0
		m.canvas.DrawObject(291,520,PauseIcon) '509 to center between the top of the progress indicator and the bottom of the center thumbnail frame
		m.canvas.DrawObject(931,520,PauseIcon) '520 to center between the top of the horizontal progress bar itself and the bottom of the center thumbnail frame
	End If
	If m.stepspeed=1
		m.canvas.DrawObject(291,520,FSlowIcon) '509 to center between the top of the progress indicator and the bottom of the center thumbnail frame
		m.canvas.DrawObject(931,520,FSlowIcon) '520 to center between the top of the horizontal progress bar itself and the bottom of the center thumbnail frame
	End If
	If m.stepspeed=2
		m.canvas.DrawObject(291,520,FFastIcon1) '509 to center between the top of the progress indicator and the bottom of the center thumbnail frame
		m.canvas.DrawObject(931,520,FFastIcon1) '520 to center between the top of the horizontal progress bar itself and the bottom of the center thumbnail frame
	End If
	If m.stepspeed=4
		m.canvas.DrawObject(291,520,FFastIcon2) '509 to center between the top of the progress indicator and the bottom of the center thumbnail frame
		m.canvas.DrawObject(931,520,FFastIcon2) '520 to center between the top of the horizontal progress bar itself and the bottom of the center thumbnail frame
	End If
	If m.stepspeed=6
		m.canvas.DrawObject(291,520,FFastIcon3) '509 to center between the top of the progress indicator and the bottom of the center thumbnail frame
		m.canvas.DrawObject(931,520,FFastIcon3) '520 to center between the top of the horizontal progress bar itself and the bottom of the center thumbnail frame
	End If
	m.canvas.DrawObject(80,574,ProgressBar)
	m.canvas.DrawObject(720,574,ProgressBar)
	
	'Draw initial position information graphical data
	m.canvas.DrawObject(152+INT(300*(m.initialposition/episode.Runtime)),574+62,ProgressMarkerArrow) 'offset +62 Y location from main marker
	m.canvas.DrawObject(792+INT(300*(m.initialposition/episode.Runtime)),574+62,ProgressMarkerArrow) 'offset +62 Y location from main marker
	'Draw initial position SHADED/COLORED PROGRESS BAR
	temporarywidth=INT(300*(m.initialposition/episode.Runtime))
	If temporarywidth>0
		ShadedBar=CreateObject("roBitmap",{width:temporarywidth,height:18,alphaenable:false})
		ShadedBar.Clear(&hFFFF) 'Blue
		m.canvas.DrawObject(172,605,ShadedBar)
		m.canvas.DrawObject(812,605,ShadedBar)
	End If
	
	m.canvas.DrawObject(152+INT(300*(m.Position/episode.Runtime)),574,ProgressMarker) 'progress marker is drawn on top
	m.canvas.DrawObject(792+INT(300*(m.Position/episode.Runtime)),574,ProgressMarker)

	P1=""
	P2=""
	'Draw Right side total runtime length of episode
	If episode.hours>0 P1=itostr(episode.hours)+"h "
	If episode.minutes<10
		P2="0"+itostr(episode.minutes)+"m"
	Else
		P2=itostr(episode.minutes)+"m"
	End If
	m.canvas.DrawText(P1+P2,486,598,&h0000FFFF,m.Font)
	m.canvas.DrawText(P1+P2,1126,598,&h0000FFFF,m.Font)
	
	'Draw Left side current position as hours and minutes
	CHours=0
	CMinutes=0
	CSeconds=m.Position
	CHours=INT(CSeconds/3600) '60seconds*60minutes
	CMinutes=INT((CSeconds-CHours*3600)/60)
	P3=""
	P4=""
	If CHours>0 P3=itostr(CHours)+"h "
	If CMinutes<10
		P4="0"+itostr(CMinutes)+"m"
	Else
		P4=itostr(CMinutes)+"m"
	End If
	m.SizeResult=m.FontMetrics.Size(P3+P4) 'determine size of string
	m.canvas.DrawText(P3+P4,140-m.SizeResult.W,598,&hFF0000FF,m.Font) 'right-justify text string
	m.canvas.DrawText(P3+P4,140-m.SizeResult.W+640,598,&hFF0000FF,m.Font)
	
	m.canvas.SwapBuffers()
End Sub

Hi @RokuTomC ,

reopening this old ticket, is there any plan to enable BIF thumbnails for a custom player on SceneGraph?

Or atleast API to parse a sprite image for the player?

regards

GM

I have the same concern. I want to implement a custom seek bar, to match the visual theme of the rest of my channel, but I would hate to lose the thumbnail pictures (and keeping them is a requirement for channel certification.)

Ideally, the Video node would provide a way to obtain the thumbnail images, irrespective of whether they come from BIF or from “standard” HLS/DASH.

In the absence of any better solution, I tried destruk’s parsing code above, and it works for finding the offsets of each jpg image inside the bif file. It is a shame that roUrlTransfer has only GetToString and GetToFile, and does not have a GetToByteArray, instead destruk uses the workaround of downloading to a file on tmp: and then loading that file into a byte array.

destruk’s code does not show how to display the images. Again, I found no way that a Poster could accept JPG data from a byte array, so again we need to go though a file on “tmp:”, cutting out a “section” of the byte array, like below.

m.dataset.WriteFile("tmp:/frame.png", m.OffsetTable[index], m.OffsetTable[index + 1] - m.OffsetTable[index])
poster.uri = "tmp:/frame.png"

None of this is pretty, but it does work.

I have found that downloading a couple of megabytes of BIF file can take 2-3 seconds, and I am wondering if it could be faster to initially download only say the first 10kB of the BIF file, to grab the index table, and then load maybe 100kB “chunks” of the file as needed.

It probably would be too inefficient to download a single image per HTTP transaction, though that could save some byte array juggling.

By the way, the way to download parts of a file (provided that the HTTP server supports it), is to add a header like this:

urlXfer.AddHeader("Range", "bytes=0-10240")