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