Roku Developer Program

Join our online forum to talk to Roku developers and fellow channel creators. Ask questions, share tips with the community, and find helpful resources.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
zig_zag
Visitor

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
0 Kudos
9 REPLIES 9
RokuTomC
Community Moderator
Community Moderator

Re: Using BIF files

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?
0 Kudos
zig_zag
Visitor

Re: Using BIF files

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
0 Kudos
RokuTomC
Community Moderator
Community Moderator

Re: Using BIF files

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
0 Kudos
destruk
Binge Watcher

Re: Using BIF files

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


0 Kudos
destruk
Binge Watcher

Re: Using BIF files

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.
0 Kudos
destruk
Binge Watcher

Re: Using BIF files

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

0 Kudos
gomad
Roku Guru

Re: Using BIF files

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

0 Kudos
JesUltra
Streaming Star

Re: Thumbnails with custom seek bar

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.

0 Kudos
JesUltra
Streaming Star

Re: Thumbnails with custom seek bar

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")

 

0 Kudos