import cffi import os import functools from datetime import timedelta from tqdm import tqdm def loadlib(dll_path,*includes,**kwargs): ffi = cffi.FFI() for include in includes: ffi.cdef(open(include).read(),kwargs) return ffi,ffi.dlopen(dll_path) class DVDError(Exception): pass class DVDNav(object): def __init__(self,path,verbose=2,method="disc"): os.environ["DVDCSS_VERBOSE"]=str(verbose) os.environ["DVDCSS_METHOD"]=method self.dvd=None self.ffi,self.lib = loadlib("libdvdnav-4.dll", "dvd_types.h", "dvd_reader.h", "ifo_types.h", "nav_types.h", "dvdnav_events.h", "dvdnav.h") self.path=path self.titles={} self.open(path) def __del__(self): self.__check_error(self.lib.dvdnav_close(self.dvd)) self.dvd=None def __repr__(self): return " {vts.new_vtsN} ({vts.new_domain} {new_domain})") if vts.new_domain==8: # back to menu break else: progbar.write(f"[{title}|{angle}] Unhandled: {events.get(ev[0],ev[0])} {size[0]}") def __check_error(self,ret): if ret==self.lib.DVDNAV_STATUS_ERR: if self.dvd: err=self.ffi.string(self.lib.dvdnav_err_to_string(self.dvd)) raise DVDError(err) raise DVDError("Unknown error") def __get_titles(self): titles=self.ffi.new("int32_t*",0) p_times=self.ffi.new("uint64_t[]",512) times=self.ffi.new("uint64_t**",p_times) duration=self.ffi.new("uint64_t*",0) titles=self.ffi.new("int32_t*",0) self.lib.dvdnav_get_number_of_titles(self.dvd,titles) num_titles=titles[0] for title in range(0,num_titles+1): if self.lib.dvdnav_get_number_of_parts(self.dvd,title,titles)==0: continue num_parts=titles[0] self.lib.dvdnav_get_number_of_angles(self.dvd,title,titles) num_angles=titles[0] num_chapters=self.lib.dvdnav_describe_title_chapters(self.dvd,title,times,duration) if duration[0]==0: continue chapters=[] for t in range(num_chapters): chapters.append(timedelta(seconds=times[0][t]/90000)) self.titles[title]={ 'parts':num_parts, 'angles': num_angles, 'duration': timedelta(seconds=duration[0]/90000), 'chapters': chapters } def __get_info(self): s=self.ffi.new("char**",self.ffi.NULL) self.lib.dvdnav_get_title_string(self.dvd,s) self.title=str(self.ffi.string(s[0]),"utf8").strip() or None self.lib.dvdnav_get_serial_string(self.dvd,s) self.serial=str(self.ffi.string(s[0]),"utf8").strip() or None self.__get_titles() def open(self,path): audio_attrs=self.ffi.new("audio_attr_t*") spu_attr=self.ffi.new("subp_attr_t*") dvdnav=self.ffi.new("dvdnav_t**",self.ffi.cast("dvdnav_t*",0)) self.__check_error(self.lib.dvdnav_open(dvdnav,bytes(path,"utf8"))) self.dvd=dvdnav[0] self.__check_error(self.lib.dvdnav_set_readahead_flag(self.dvd,1)) self.__get_info() for title in self.titles: self.__check_error(self.lib.dvdnav_title_play(self.dvd,title)) self.titles[title]['audio']={} self.titles[title]['subtitles']={} for n in range(255): stream_id=self.lib.dvdnav_get_audio_logical_stream(self.dvd,n) if stream_id==-1: continue self.__check_error(self.lib.dvdnav_get_audio_attr(self.dvd,stream_id,audio_attrs)) alang=None if audio_attrs.lang_type: alang = str(audio_attrs.lang_code.to_bytes(2,'big'),"utf8") channels = audio_attrs.channels+1 codec = { 0: 'ac3', 2: 'mpeg1', 3: 'mpeg-2ext', 4: 'lpcm', 6: 'dts' }[audio_attrs.audio_format] audio_type={ 0: None, 1: 'normal', 2: 'descriptive', 3: "director's commentary", 4: "alternate director's commentary" }[audio_attrs.code_extension] self.titles[title]['audio'][n]={ 'stream_id': stream_id, 'lang':alang, 'channels': channels, 'codec': codec, 'type': audio_type } for n in range(255): stream_id=self.lib.dvdnav_get_spu_logical_stream(self.dvd,n) if stream_id==-1: continue self.__check_error(self.lib.dvdnav_get_spu_attr(self.dvd,stream_id,spu_attr)) slang = None if spu_attr.type==1: slang = str(spu_attr.lang_code.to_bytes(2,'big'),"utf8") self.titles[title]['subtitles'][n]={ 'stream_id': stream_id, 'lang':slang } self.__check_error(self.lib.dvdnav_stop(self.dvd))