1 # Module: dvdtitlestream.py
2 # Author: Eric von Bayer
4 # Date: August 18, 2009
6 # Class that works like a file but is in reality a series of file system
7 # files along with a sector map. This in conjunction with the underlying
8 # composite file will allow linear file access to the convoluted underlying
9 # video stream. Intentionally is a similar API to the file object.
11 # Copyright (c) 2009, Eric von Bayer
12 # All rights reserved.
14 # Redistribution and use in source and binary forms, with or without
15 # modification, are permitted provided that the following conditions are met:
17 # * Redistributions of source code must retain the above copyright notice,
18 # this list of conditions and the following disclaimer.
19 # * Redistributions in binary form must reproduce the above copyright notice,
20 # this list of conditions and the following disclaimer in the documentation
21 # and/or other materials provided with the distribution.
22 # * The names of the contributors may not be used to endorse or promote
23 # products derived from this software without specific prior written
26 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
30 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
34 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 from compositefile
import CompositeFile
44 except AttributeError:
45 os
.SEEK_SET
, os
.SEEK_CUR
, os
.SEEK_END
= range(3)
47 class DVDTitleStream(object):
48 """Virtual file that is a composite of vob files mapped through a sector list"""
49 def __init__( self
, *files
):
50 if len(files
) == 1 and isinstance( files
[0], DVDTitleStream
):
51 self
.__cfile
= CompositeFile( files
[0].__cfile
)
52 self
.__slist
= files
[0].__slist
54 self
.__cfile
= CompositeFile( *files
)
58 self
.__sector
_map
= None
62 self
.__next
_sec
_off
= 0L
67 for [s
,e
] in self
.__slist
:
68 ser
.append( "[%d-%d]" % (s
,e
) )
69 return "[" + str( self
.__cfile
) + "] % " + ",".join( ser
)
71 # Build a map of ( virtual offset, real offset )
72 def __map_sectors( self
):
73 if self
.__sector
_map
== None:
75 self
.__sector
_map
= list()
77 for (s
,e
) in self
.__slist
:
78 off
+= ( e
- s
+ 1 ) * DVD_BLOCK_LEN
79 self
.__sector
_map
.append( ( off
, s
* DVD_BLOCK_LEN
) )
82 self
.__next
_sec
_off
= self
.__sector
_map
[0][0]
85 # Advance to the next file
86 def __next_range( self
):
89 # Bump the file number that we're on
92 # If there's another sector range, seek to it and get the next offset
93 if self
.__srange
< len( self
.__sector
_map
):
94 self
.__cfile
.seek( self
.__sector
_map
[self
.__srange
][1] )
95 self
.__next
_sec
_off
= self
.__sector
_map
[self
.__srange
][0]
97 # We're done, close the file
102 # Read a slice, return the data and how many bytes remain to be read
103 def __read_slice( self
, bytes
):
106 rem
= self
.__off
+ bytes
- self
.__next
_sec
_off
108 # If the remaining bytes aren't negative, then read a slice and advance
112 data
= self
.__cfile
.read( bytes
)
113 self
.__off
+= len(data
)
117 # Read the bytes all from this file
119 data
= self
.__cfile
.read( bytes
)
120 self
.__off
+= len(data
)
125 def AddSectors( self
, start
, end
):
126 self
.__sects
+= (end
- start
) + 1
127 if len(self
.__slist
) > 0:
128 if self
.__slist
[-1][1]+1 == start
:
129 self
.__slist
[-1][1] = end
131 self
.__slist
.append( [ start
, end
] )
132 self
.__sector
_map
= None
134 def SectorList( self
):
138 return self
.__cfile
.files()
140 def tell_real( self
):
141 return self
.__cfile
.tell()
146 # Seek into the composite file
147 def seek( self
, off
, whence
= os
.SEEK_SET
):
150 # Calculate the new seek offset
151 if whence
== os
.SEEK_SET
:
153 elif whence
== os
.SEEK_CUR
:
154 new_off
= self
.__off
+ off
155 elif whence
== os
.SEEK_END
:
156 new_off
= self
.__sector
_map
[-1][0] + off
158 raise "seek called with an invalid offset type"
160 # Determine which file this seek offset is part of
163 for ( eoff
, roff
) in self
.__sector
_map
:
169 # Make sure this was a valid seek point
171 raise "seek beyond the bounds of the composite file"
173 # Make sure the correct file is open
174 if srange
!= self
.__srange
:
175 self
.__next
_sec
_off
= eoff
176 self
.__srange
= srange
178 # Seek the file handle
179 self
.__cfile
.seek( roff
+ new_off
- soff
)
183 def read( self
, bytes
):
185 if self
.closed
== False:
187 while bytes
> 0 and self
.closed
== False:
188 slice_data
, bytes
= self
.__read
_slice
( bytes
)
195 if self
.__cfile
.closed
== False:
200 return self
.__sects
* DVD_BLOCK_LEN