Push latest changes

This commit is contained in:
Daniel S. 2021-10-28 21:27:15 +02:00
parent be4930a8a3
commit aeb624bcf3
5 changed files with 578 additions and 320 deletions

View file

@ -2,37 +2,64 @@ import cffi
import os import os
import sys import sys
import time import time
from dvdnav import DVDNav from dvdnav import DVDNav,DVDError
from dvdread import DVDRead from dvdread import DVDRead
import subprocess as SP import subprocess as SP
import json import json
from glob import glob
import itertools as ITT
from vob_demux import demux from vob_demux import demux
from ff_d2v import make_d2v from ff_d2v import make_d2v
def loadlib(dll_path,*includes,**kwargs):
def loadlib(dll_path, *includes, **kwargs):
ffi = cffi.FFI() ffi = cffi.FFI()
for include in includes: for include in includes:
ffi.cdef(open(include).read(),kwargs) ffi.cdef(open(include).read(), kwargs)
return ffi,ffi.dlopen(dll_path) return ffi, ffi.dlopen(dll_path)
os.environ["DVDCSS_VERBOSE"]="2" for dvd_path in ITT.chain.from_iterable(map(glob,sys.argv[1:])):
os.environ["DVDCSS_METHOD"]="disc" r = DVDRead(dvd_path)
# r.grab_ifos()
# r.grab_vobs()
# exit()
r=DVDRead(sys.argv[1]) out_folder = os.path.join(
out_folder=os.path.join("out","_".join([r.disc_id,r.udf_disc_name or r.iso_disc_name]).replace(" ","_")) "out", "_".join([r.disc_id, r.udf_disc_name or r.iso_disc_name]).replace(" ", "_")
del r )
os.makedirs(out_folder,exist_ok=True) os.makedirs(out_folder, exist_ok=True)
d=DVDNav(sys.argv[1]) d = DVDNav(dvd_path)
for k,v in d.titles.items(): to_demux = []
v['duration']=v['duration'].total_seconds() for k, v in d.titles.items():
v['chapters']=[c.total_seconds() for c in v['chapters']] v["duration"] = v["duration"].total_seconds()
d.titles[k]=v v["chapters"] = [c.total_seconds() for c in v["chapters"]]
with open(os.path.join(out_folder,f"{k}.json"),"w") as fh: d.titles[k] = v
json.dump(v,fh) with open(os.path.join(out_folder, f"{k:03}.json"), "w") as fh:
for a in range(v['angles']): json.dump(v, fh)
a+=1 for a in range(0,99):
outfile=os.path.join(out_folder,f"{k}_{a}.vob") block=0
with open(outfile,"wb") as fh: outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob")
for block in d.get_blocks(k,a): to_demux.append(outfile)
fh.write(block) fh = open(outfile, "wb")
demux(outfile) try:
os.unlink(outfile) for block in d.get_blocks(k, a):
if isinstance(block, int):
outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob")
to_demux.append(outfile)
if fh:
fh.close()
fh = open(outfile, "wb")
else:
fh.write(block)
except DVDError as e:
if str(e)!="Invalid angle specified!":
raise
if fh.tell()==0:
fh.close()
os.unlink(fh.name)
while fh.name in to_demux:
to_demux.remove(fh.name)
for file in to_demux:
demux(file)
os.unlink(file)
for file in glob(os.path.join(out_folder,"*.m2v")):
make_d2v(file)

313
dvdnav.py
View file

@ -3,58 +3,77 @@ import os
import functools import functools
from datetime import timedelta from datetime import timedelta
from tqdm import tqdm from tqdm import tqdm
from dvdread import DVDRead
def loadlib(dll_path,*includes,**kwargs):
def loadlib(dll_path, *includes, **kwargs):
ffi = cffi.FFI() ffi = cffi.FFI()
for include in includes: for include in includes:
ffi.cdef(open(include).read(),kwargs) ffi.cdef(open(include).read(), kwargs)
return ffi,ffi.dlopen(dll_path) return ffi, ffi.dlopen(dll_path)
class DVDError(Exception): class DVDError(Exception):
pass pass
class DVDNav(object): class DVDNav(object):
def __init__(self,path,verbose=2,method="disc"): def __init__(self, path, verbose=None, method="disc"):
os.environ["DVDCSS_VERBOSE"]=str(verbose) if verbose is None:
os.environ["DVDCSS_METHOD"]=method os.environ.pop("DVDCSS_VERBOSE", None)
self.dvd=None else:
self.ffi,self.lib = loadlib("libdvdnav-4.dll", 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_types.h",
"dvd_reader.h", "dvd_reader.h",
"ifo_types.h", "ifo_types.h",
"nav_types.h", "nav_types.h",
"dvdnav_events.h", "dvdnav_events.h",
"dvdnav.h") "dvdnav.h",
self.path=path pack=True,
self.titles={} )
self.path = path
self.titles = {}
self.open(path) self.open(path)
def __del__(self): def __del__(self):
self.__check_error(self.lib.dvdnav_close(self.dvd)) self.__check_error(self.lib.dvdnav_close(self.dvd))
self.dvd=None self.dvd = None
def __repr__(self): def __repr__(self):
return "<DVD Path={0.path} Title={0.title} Serial={0.serial}".format(self) return "<DVD Path={0.path} Title={0.title} Serial={0.serial}".format(self)
def get_blocks(self,title,angle=1,slang=None): def get_blocks(self, title, angle=1, slang=None):
self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd,1)) self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1))
self.__check_error(self.lib.dvdnav_title_play(self.dvd,title)) self.__check_error(self.lib.dvdnav_title_play(self.dvd, title))
self.__check_error(self.lib.dvdnav_angle_change(self.dvd,angle)) curr_angle = self.ffi.new("int32_t*", 0)
num_angles = self.ffi.new("int32_t*", 0)
self.__check_error(
self.lib.dvdnav_get_angle_info(self.dvd, curr_angle, num_angles)
)
if angle != 0:
if angle < 1 or angle > num_angles[0]:
raise DVDError("Invalid angle specified!")
if angle != curr_angle[0]:
self.__check_error(self.lib.dvdnav_angle_change(self.dvd, angle))
if slang is not None: if slang is not None:
self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd,slang)) self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd, slang))
event=self.lib.DVDNAV_NOP event = self.lib.DVDNAV_NOP
buf=self.ffi.new("char[]",4096) buf = self.ffi.new("char[]", 4096)
ev=self.ffi.new("int32_t*",self.lib.DVDNAV_NOP) ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP)
size=self.ffi.new("int32_t*",0) size = self.ffi.new("int32_t*", 0)
pos=self.ffi.new("uint32_t*",0) pos = self.ffi.new("uint32_t*", 0)
total_size=self.ffi.new("uint32_t*",0) total_size = self.ffi.new("uint32_t*", 0)
domains = { domains = {
1: "FirstPlay", 1: "FirstPlay",
2: "VTSTitle", 2: "VTSTitle",
4: "VMGM", 4: "VMGM",
8: "VTSMenu" 8: "VTSMenu",
} }
events={ events = {
0: "DVDNAV_BLOCK_OK", 0: "DVDNAV_BLOCK_OK",
1: "DVDNAV_NOP", 1: "DVDNAV_NOP",
2: "DVDNAV_STILL_FRAME", 2: "DVDNAV_STILL_FRAME",
@ -67,146 +86,180 @@ class DVDNav(object):
9: "DVDNAV_HIGHLIGHT", 9: "DVDNAV_HIGHLIGHT",
10: "DVDNAV_SPU_CLUT_CHANGE", 10: "DVDNAV_SPU_CLUT_CHANGE",
12: "DVDNAV_HOP_CHANNEL", 12: "DVDNAV_HOP_CHANNEL",
13: "DVDNAV_WAIT" 13: "DVDNAV_WAIT",
} }
progbar=tqdm(unit_divisor=1024,unit_scale=True,unit="iB",desc="Ripping DVD") progbar = tqdm(
unit_divisor=1024,
unit_scale=True,
unit="iB",
desc="Ripping DVD",
disable=False,
)
ripped = set()
current_vts = None
current_cell = None
current_pg = None
while True: while True:
self.__check_error(self.lib.dvdnav_get_next_block(self.dvd,buf,ev,size)) self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size))
if self.lib.dvdnav_get_position(self.dvd,pos,total_size)==self.lib.DVDNAV_STATUS_OK: if (
progbar.total=total_size[0]*2048 self.lib.dvdnav_get_position(self.dvd, pos, total_size)
progbar.n=max(progbar.n,min(progbar.total,pos[0]*2048)) == self.lib.DVDNAV_STATUS_OK
):
progbar.total = total_size[0] * 2048
progbar.n = max(progbar.n, min(progbar.total, pos[0] * 2048))
progbar.update(0) progbar.update(0)
progbar.set_postfix(
vts=current_vts,
cell=current_cell,
pg=current_pg,
angle=angle,
title=title,
)
# print("Got event:",events.get(ev[0],ev[0]),size[0]) # print("Got event:",events.get(ev[0],ev[0]),size[0])
if ev[0] in [self.lib.DVDNAV_SPU_CLUT_CHANGE,self.lib.DVDNAV_HOP_CHANNEL,self.lib.DVDNAV_NOP]: if ev[0] in [
self.lib.DVDNAV_SPU_CLUT_CHANGE,
self.lib.DVDNAV_HOP_CHANNEL,
self.lib.DVDNAV_NOP,
self.lib.DVDNAV_HIGHLIGHT,
]:
continue continue
elif ev[0]==self.lib.DVDNAV_BLOCK_OK: elif ev[0] == self.lib.DVDNAV_BLOCK_OK:
# print("Read",size[0]) yield self.ffi.buffer(buf, size[0])[:]
yield self.ffi.buffer(buf,size[0])[:] elif ev[0] == self.lib.DVDNAV_STOP:
elif ev[0]==self.lib.DVDNAV_STOP: progbar.write(f"[{title}|{angle}] Stop")
break break
elif ev[0]==self.lib.DVDNAV_NAV_PACKET: elif ev[0] == self.lib.DVDNAV_NAV_PACKET:
nav=self.lib.dvdnav_get_current_nav_pci(self.dvd) pass
# print("PTS:",timedelta(seconds=nav.pci_gi.vobu_s_ptm/90000)) elif ev[0] == self.lib.DVDNAV_STILL_FRAME:
elif ev[0]==self.lib.DVDNAV_STILL_FRAME:
# print("Still")
self.__check_error(self.lib.dvdnav_still_skip(self.dvd)) self.__check_error(self.lib.dvdnav_still_skip(self.dvd))
elif ev[0]==self.lib.DVDNAV_WAIT: elif ev[0] == self.lib.DVDNAV_WAIT:
# print("Wait",size[0])
self.__check_error(self.lib.dvdnav_wait_skip(self.dvd)) self.__check_error(self.lib.dvdnav_wait_skip(self.dvd))
elif ev[0]==self.lib.DVDNAV_SPU_STREAM_CHANGE: elif ev[0] == self.lib.DVDNAV_SPU_STREAM_CHANGE:
spu=self.ffi.cast("dvdnav_spu_stream_change_event_t*",buf) pass
progbar.write(f"[{title}|{angle}] SPU: Wide: {spu.physical_wide} Letterbox: {spu.physical_letterbox} Pan&Scan: {spu.physical_pan_scan} Logical: {spu.logical}") elif ev[0] == self.lib.DVDNAV_AUDIO_STREAM_CHANGE:
elif ev[0]==self.lib.DVDNAV_AUDIO_STREAM_CHANGE: audio = self.ffi.cast("dvdnav_audio_stream_change_event_t*", buf)
audio=self.ffi.cast("dvdnav_audio_stream_change_event_t*",buf) elif ev[0] == self.lib.DVDNAV_CELL_CHANGE:
progbar.write(f"[{title}|{angle}] Audio: Physical: {audio.physical} Logical: {audio.logical}") cell = self.ffi.cast("dvdnav_cell_change_event_t*", buf)
elif ev[0]==self.lib.DVDNAV_CELL_CHANGE: current_cell = cell.cellN
cell=self.ffi.cast("dvdnav_cell_change_event_t*",buf) current_pg = cell.pgN
progbar.write(f"[{title}|{angle}] Cell: {cell.cellN} ({cell.cell_start}-{cell.cell_start+cell.cell_length}), PG: {cell.pgN} ({cell.pg_start}-{cell.pg_start+cell.pg_length})") progbar.write(
elif ev[0]==self.lib.DVDNAV_VTS_CHANGE: f"[{title}|{angle}] Cell: {cell.cellN} ({cell.cell_start}-{cell.cell_start+cell.cell_length}), PG: {cell.pgN} ({cell.pg_start}-{cell.pg_start+cell.pg_length})"
vts=self.ffi.cast("dvdnav_vts_change_event_t*",buf) )
old_domain=sorted(domains[k] for k in domains if vts.old_domain&k) elif ev[0] == self.lib.DVDNAV_VTS_CHANGE:
new_domain=sorted(domains[k] for k in domains if vts.new_domain&k) vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf)
progbar.write(f"[{title}|{angle}] VTS: {vts.old_vtsN} ({vts.old_domain} {old_domain}) -> {vts.new_vtsN} ({vts.new_domain} {new_domain})") new_vts = (vts.new_vtsN, vts.new_domain)
if vts.new_domain==8: # back to menu ripped.add((vts.old_vtsN, vts.old_domain))
# progbar.write(f"[{title}|{angle}] VTS: {vts.old_vtsN} ({vts.old_domain} {old_domain}) -> {vts.new_vtsN} ({vts.new_domain} {new_domain})")
if new_vts in ripped: # looped
progbar.write(f"[{title}|{angle}] Looped!")
break break
current_vts = (vts.new_vtsN, vts.new_domain)
if vts.new_domain == 8: # back to menu
progbar.write(f"[{title}|{angle}] Back to menu!")
break
yield vts.new_vtsN
else: else:
progbar.write(f"[{title}|{angle}] Unhandled: {events.get(ev[0],ev[0])} {size[0]}") progbar.write(
f"[{title}|{angle}] Unhandled: {events.get(ev[0],ev[0])} {size[0]}"
)
self.__check_error(self.lib.dvdnav_stop(self.dvd))
def __check_error(self, ret):
def __check_error(self,ret): if ret == self.lib.DVDNAV_STATUS_ERR:
if ret==self.lib.DVDNAV_STATUS_ERR:
if self.dvd: if self.dvd:
err=self.ffi.string(self.lib.dvdnav_err_to_string(self.dvd)) err = self.ffi.string(self.lib.dvdnav_err_to_string(self.dvd))
raise DVDError(err) raise DVDError(err)
raise DVDError("Unknown error") raise DVDError("Unknown error")
def __get_titles(self): def __get_titles(self):
titles=self.ffi.new("int32_t*",0) titles = self.ffi.new("int32_t*", 0)
p_times=self.ffi.new("uint64_t[]",512) p_times = self.ffi.new("uint64_t[]", 512)
times=self.ffi.new("uint64_t**",p_times) times = self.ffi.new("uint64_t**", p_times)
duration=self.ffi.new("uint64_t*",0) duration = self.ffi.new("uint64_t*", 0)
titles=self.ffi.new("int32_t*",0) titles = self.ffi.new("int32_t*", 0)
self.lib.dvdnav_get_number_of_titles(self.dvd,titles) self.lib.dvdnav_get_number_of_titles(self.dvd, titles)
num_titles=titles[0] num_titles = titles[0]
for title in range(0,num_titles+1): for title in range(0, num_titles + 1):
if self.lib.dvdnav_get_number_of_parts(self.dvd,title,titles)==0: if self.lib.dvdnav_get_number_of_parts(self.dvd, title, titles) == 0:
continue continue
num_parts=titles[0] num_parts = titles[0]
self.lib.dvdnav_get_number_of_angles(self.dvd,title,titles) self.lib.dvdnav_get_number_of_angles(self.dvd, title, titles)
num_angles=titles[0] num_angles = titles[0]
num_chapters=self.lib.dvdnav_describe_title_chapters(self.dvd,title,times,duration) num_chapters = self.lib.dvdnav_describe_title_chapters(
if duration[0]==0: self.dvd, title, times, duration
)
if duration[0] == 0:
continue continue
chapters=[] chapters = []
for t in range(num_chapters): for t in range(num_chapters):
chapters.append(timedelta(seconds=times[0][t]/90000)) chapters.append(timedelta(seconds=times[0][t] / 90000))
self.titles[title]={ self.titles[title] = {
'parts':num_parts, "parts": num_parts,
'angles': num_angles, "angles": num_angles,
'duration': timedelta(seconds=duration[0]/90000), "duration": timedelta(seconds=duration[0] / 90000),
'chapters': chapters "chapters": chapters,
} }
def __get_info(self): def __get_info(self):
s=self.ffi.new("char**",self.ffi.NULL) s = self.ffi.new("char**", self.ffi.NULL)
self.lib.dvdnav_get_title_string(self.dvd,s) self.lib.dvdnav_get_title_string(self.dvd, s)
self.title=str(self.ffi.string(s[0]),"utf8").strip() or None self.title = str(self.ffi.string(s[0]), "utf8").strip() or None
self.lib.dvdnav_get_serial_string(self.dvd,s) self.lib.dvdnav_get_serial_string(self.dvd, s)
self.serial=str(self.ffi.string(s[0]),"utf8").strip() or None self.serial = str(self.ffi.string(s[0]), "utf8").strip() or None
self.__get_titles() self.__get_titles()
def open(self,path): def open(self, path):
audio_attrs=self.ffi.new("audio_attr_t*") audio_attrs = self.ffi.new("audio_attr_t*")
spu_attr=self.ffi.new("subp_attr_t*") spu_attr = self.ffi.new("subp_attr_t*")
dvdnav=self.ffi.new("dvdnav_t**",self.ffi.cast("dvdnav_t*",0)) dvdnav = self.ffi.new("dvdnav_t**", self.ffi.cast("dvdnav_t*", 0))
self.__check_error(self.lib.dvdnav_open(dvdnav,bytes(path,"utf8"))) self.__check_error(self.lib.dvdnav_open(dvdnav, bytes(path, "utf8")))
self.dvd=dvdnav[0] self.dvd = dvdnav[0]
self.__check_error(self.lib.dvdnav_set_readahead_flag(self.dvd,1)) self.__check_error(self.lib.dvdnav_set_readahead_flag(self.dvd, 1))
self.__get_info() self.__get_info()
for title in self.titles: for title in self.titles:
self.__check_error(self.lib.dvdnav_title_play(self.dvd,title)) self.__check_error(self.lib.dvdnav_title_play(self.dvd, title))
self.titles[title]['audio']={} self.titles[title]["audio"] = {}
self.titles[title]['subtitles']={} self.titles[title]["subtitles"] = {}
for n in range(255): for n in range(255):
stream_id=self.lib.dvdnav_get_audio_logical_stream(self.dvd,n) stream_id = self.lib.dvdnav_get_audio_logical_stream(self.dvd, n)
if stream_id==-1: if stream_id == -1:
continue continue
self.__check_error(self.lib.dvdnav_get_audio_attr(self.dvd,stream_id,audio_attrs)) self.__check_error(
alang=None self.lib.dvdnav_get_audio_attr(self.dvd, stream_id, audio_attrs)
)
alang = None
if audio_attrs.lang_type: if audio_attrs.lang_type:
alang = str(audio_attrs.lang_code.to_bytes(2,'big'),"utf8") alang = str(audio_attrs.lang_code.to_bytes(2, "big"), "utf8")
channels = audio_attrs.channels+1 channels = audio_attrs.channels + 1
codec = { codec = {0: "ac3", 2: "mpeg1", 3: "mpeg-2ext", 4: "lpcm", 6: "dts"}[
0: 'ac3', audio_attrs.audio_format
2: 'mpeg1', ]
3: 'mpeg-2ext', audio_type = {
4: 'lpcm',
6: 'dts'
}[audio_attrs.audio_format]
audio_type={
0: None, 0: None,
1: 'normal', 1: "normal",
2: 'descriptive', 2: "descriptive",
3: "director's commentary", 3: "director's commentary",
4: "alternate director's commentary" 4: "alternate director's commentary",
}[audio_attrs.code_extension] }[audio_attrs.code_extension]
self.titles[title]['audio'][n]={ self.titles[title]["audio"][n] = {
'stream_id': stream_id, "stream_id": stream_id,
'lang':alang, "lang": alang,
'channels': channels, "channels": channels,
'codec': codec, "codec": codec,
'type': audio_type "type": audio_type,
} }
for n in range(255): for n in range(255):
stream_id=self.lib.dvdnav_get_spu_logical_stream(self.dvd,n) stream_id = self.lib.dvdnav_get_spu_logical_stream(self.dvd, n)
if stream_id==-1: if stream_id == -1:
continue continue
self.__check_error(self.lib.dvdnav_get_spu_attr(self.dvd,stream_id,spu_attr)) self.__check_error(
self.lib.dvdnav_get_spu_attr(self.dvd, stream_id, spu_attr)
)
slang = None slang = None
if spu_attr.type==1: if spu_attr.type == 1:
slang = str(spu_attr.lang_code.to_bytes(2,'big'),"utf8") slang = str(spu_attr.lang_code.to_bytes(2, "big"), "utf8")
self.titles[title]['subtitles'][n]={ self.titles[title]["subtitles"][n] = {
'stream_id': stream_id, "stream_id": stream_id,
'lang':slang "lang": slang,
} }
self.__check_error(self.lib.dvdnav_stop(self.dvd)) self.__check_error(self.lib.dvdnav_stop(self.dvd))

View file

@ -4,16 +4,24 @@ import functools
import binascii import binascii
from datetime import timedelta from datetime import timedelta
def loadlib(dll_path,*includes,**kwargs):
def loadlib(dll_path, *includes, **kwargs):
ffi = cffi.FFI() ffi = cffi.FFI()
for include in includes: for include in includes:
ffi.cdef(open(include).read(),**kwargs) ffi.cdef(open(include).read(), **kwargs)
return ffi,ffi.dlopen(dll_path) return ffi, ffi.dlopen(dll_path)
class DVDRead(object): class DVDRead(object):
def __init__(self,path): def __init__(self, path, verbose="0", method="disc"):
self.dvd=None if verbose is None:
self.ffi,self.lib = loadlib("libdvdread-8.dll", os.environ.pop("DVDCSS_VERBOSE", None)
else:
os.environ["DVDCSS_VERBOSE"] = str(verbose)
os.environ["DVDCSS_METHOD"] = method
self.dvd = None
self.ffi, self.lib = loadlib(
"libdvdread-8.dll",
"dvd_types.h", "dvd_types.h",
"dvd_reader.h", "dvd_reader.h",
"ifo_types.h", "ifo_types.h",
@ -21,23 +29,137 @@ class DVDRead(object):
"ifo_print.h", "ifo_print.h",
"nav_types.h", "nav_types.h",
"nav_read.h", "nav_read.h",
"nav_print.h") "nav_print.h",
self.path=path packed=True,
self.titles={} )
self.path = path
self.titles = {}
self.open(path) self.open(path)
self.lb_len=self.lib.DVD_VIDEO_LB_LEN
def grab_ifo(self,title,bup=False):
from tqdm import tqdm
buf = self.ffi.new("unsigned char[]", 512)
if bup:
fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_BACKUP_FILE)
else:
fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_FILE)
total_size = self.lib.DVDFileSize(fh)*self.lb_len
remaining = total_size
num_read = True
pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False)
while num_read:
num_read=self.lib.DVDReadBytes( fh, buf, 512)
num_read=min(num_read,remaining)
remaining-=num_read
pbar.update(num_read)
yield self.ffi.buffer(buf,num_read)[:]
self.lib.DVDCloseFile(fh)
def grab_ifos(self):
vmg_ifo = self.lib.ifoOpen(self.dvd, 0)
if vmg_ifo == self.ffi.NULL:
return
title_sets = vmg_ifo.vts_atrt.nr_of_vtss
for t in range(1,title_sets + 1):
vts = self.lib.ifoOpen(self.dvd, t)
if vts == self.ffi.NULL:
continue
self.lib.ifoClose(vts)
outfile=os.path.join("RIP",f"VTS_{t:02}_0.ifo")
with open(outfile, "wb") as out_ifo:
for block in self.grab_ifo(t,bup=False):
out_ifo.write(block)
outfile=os.path.join("RIP",f"VTS_{t:02}_0.bup")
with open(outfile, "wb") as out_ifo:
for block in self.grab_ifo(t,bup=True):
out_ifo.write(block)
self.lib.ifoClose(vmg_ifo)
def grab_vob(self,title):
from tqdm import tqdm
buf = self.ffi.new("unsigned char[]", 512 * self.lb_len)
fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_TITLE_VOBS)
total_size = self.lib.DVDFileSize(fh)*self.lb_len
remaining = total_size
num_read = True
pos=0
pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False)
while remaining:
num_read=self.lib.DVDReadBlocks( fh,pos, 512, buf)
if num_read<0:
raise RuntimeError("Error reading!")
num_read_bytes=num_read*self.lb_len
num_read_bytes=min(num_read_bytes,remaining)
remaining-=num_read_bytes
pbar.update(num_read_bytes)
yield self.ffi.buffer(buf,num_read_bytes)[:]
pos+=num_read
pbar.close()
self.lib.DVDCloseFile(fh)
def grab_vobs(self):
vmg_ifo = self.lib.ifoOpen(self.dvd, 0)
if vmg_ifo == self.ffi.NULL:
return
title_sets = vmg_ifo.vts_atrt.nr_of_vtss
for t in range(1,title_sets + 1):
vts = self.lib.ifoOpen(self.dvd, t)
if vts == self.ffi.NULL:
continue
self.lib.ifoClose(vts)
outfile=os.path.join("RIP",f"VTS_{t:02}_0.vob")
with open(outfile, "wb") as out_ifo:
for block in self.grab_vob(t):
out_ifo.write(block)
self.lib.ifoClose(vmg_ifo)
def test(self):
from tqdm import tqdm
fn = 0
chunk_size = 2048
buf = self.ffi.new("unsigned char[]", chunk_size * 2048)
for fn in range(title_sets + 1):
pos = 0
fh = self.lib.DVDOpenFile(self.dvd, fn, self.lib.DVD_READ_TITLE_VOBS)
if fh:
total_size = self.lib.DVDFileSize(fh)
if total_size == -1:
self.lib.DVDCloseFile(fh)
break
pbar = tqdm(total=total_size * 2048, unit="iB", unit_scale=True, unit_divisor=1024,leave=False)
last=False
with open(f"out_{fn}.vob", "wb") as out_vob:
while True:
if (pos+chunk_size)>total_size:
chunk_size=total_size-pos
count = self.lib.DVDReadBlocks(fh, pos, chunk_size, buf)
if count == -1:
break
pbar.update(
out_vob.write(self.ffi.buffer(buf, count * 2048)[:])
)
pos += count
if pos>=total_size:
break
self.lib.DVDCloseFile(fh)
fn += 1
if fn>200:
break
def __del__(self): def __del__(self):
if self.dvd: if self.dvd:
self.lib.DVDClose(self.dvd) self.lib.DVDClose(self.dvd)
def open(self,path): def open(self, path):
# self.dvd_css=self.css_lib.dvdcss_open() # self.dvd_css=self.css_lib.dvdcss_open()
self.dvd=self.lib.DVDOpen(bytes(path,"utf8")) self.dvd = self.lib.DVDOpen(bytes(path, "utf8"))
vol_id=self.ffi.new("unsigned char[]",32) vol_id = self.ffi.new("unsigned char[]", 32)
self.lib.DVDDiscID(self.dvd,vol_id) self.lib.DVDDiscID(self.dvd, vol_id)
self.disc_id=str(binascii.hexlify(self.ffi.buffer(vol_id,16)[:]),"utf8") self.disc_id = str(binascii.hexlify(self.ffi.buffer(vol_id, 16)[:]), "utf8")
self.lib.DVDUDFVolumeInfo(self.dvd,vol_id,32,self.ffi.NULL,0) self.lib.DVDUDFVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0)
self.udf_disc_name=str(self.ffi.string(vol_id),"utf8") self.udf_disc_name = str(self.ffi.string(vol_id), "utf8")
self.lib.DVDISOVolumeInfo(self.dvd,vol_id,32,self.ffi.NULL,0) self.lib.DVDISOVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0)
self.iso_disc_name=str(self.ffi.string(vol_id),"utf8") self.iso_disc_name = str(self.ffi.string(vol_id), "utf8")
self.ffi.release(vol_id) self.ffi.release(vol_id)

236
ff_d2v.py
View file

@ -6,7 +6,7 @@ import itertools as ITT
from tqdm import tqdm from tqdm import tqdm
colorspace={ colorspace = {
"gbr": 0, "gbr": 0,
"bt709": 1, "bt709": 1,
"unknown": 2, "unknown": 2,
@ -23,151 +23,183 @@ colorspace={
"ictcp": 14, "ictcp": 14,
} }
pict_types={ pict_types = {"I": 0b01, "P": 0b10, "B": 0b11}
'I':0b01,
'P':0b10,
'B':0b11
}
def make_info(frames): def make_info(frames):
has_interlaced = any(frame['interlaced_frame'] for frame in frames) has_interlaced = any(frame["interlaced_frame"] for frame in frames)
new_gop='timecode' in frames[0].get('tags',{}) new_gop = "timecode" in frames[0].get("tags", {})
info=0x000 info = 0x000
info|=1<<11 # always 1 info |= 1 << 11 # always 1
info|=0<<10 # 0=Closed GOP, 1=Open GOP info |= 0 << 10 # 0=Closed GOP, 1=Open GOP
info|=(not has_interlaced)<<9 # Progressive info |= (not has_interlaced) << 9 # Progressive
info|=new_gop<<8 info |= new_gop << 8
return info return info
def make_flags(frames): def make_flags(frames):
flags=[] flags = []
for frame in frames: for frame in frames:
needs_prev=False needs_prev = False
progressive=not int(frame['interlaced_frame']) progressive = not int(frame["interlaced_frame"])
pt=pict_types[frame['pict_type']] pt = pict_types[frame["pict_type"]]
reserved=0b00 reserved = 0b00
tff=int(frame['top_field_first']) tff = int(frame["top_field_first"])
rff=int(frame['repeat_pict']) rff = int(frame["repeat_pict"])
flag=0b0 flag = 0b0
flag|=(not needs_prev)<<7 flag |= (not needs_prev) << 7
flag|=progressive<<6 flag |= progressive << 6
flag|=pt<<4 flag |= pt << 4
flag|=reserved<<2 flag |= reserved << 2
flag|=tff<<1 flag |= tff << 1
flag|=rff flag |= rff
flags.append(f"{flag:02x}") flags.append(f"{flag:02x}")
return flags return flags
def make_line(frames,stream): def make_line(frames, stream):
info=f"{make_info(frames):03x}" info = f"{make_info(frames):03x}"
matrix=colorspace[stream['color_space']] matrix = colorspace[stream["color_space"]]
file=0 file = 0
position=frames[0]['pkt_pos'] position = frames[0]["pkt_pos"]
skip=0 skip = 0
vob=0 vob = 0
cell=0 cell = 0
flags=make_flags(frames) flags = make_flags(frames)
return " ".join(map(str,[info,matrix,file,position,skip,vob,cell,*flags])) return " ".join(map(str, [info, matrix, file, position, skip, vob, cell, *flags]))
def get_frames(path): def get_frames(path):
proc=SP.Popen([ proc = SP.Popen(
"ffprobe", [
"-probesize", str(0x7fffffff), "ffprobe",
"-analyzeduration", str(0x7fffffff), "-probesize",
"-v","fatal", str(0x7FFFFFFF),
"-i",path, "-analyzeduration",
"-select_streams","v:0", str(0x7FFFFFFF),
"-show_frames", "-v",
"-print_format","compact" "fatal",
],stdout=SP.PIPE,stdin=SP.DEVNULL,bufsize=0) "-i",
data=None path,
"-select_streams",
"v:0",
"-show_frames",
"-print_format",
"compact",
],
stdout=SP.PIPE,
stdin=SP.DEVNULL,
bufsize=0,
)
data = None
for line in proc.stdout: for line in proc.stdout:
line=str(line,"utf8").strip().split("|") line = str(line, "utf8").strip().split("|")
line={line[0]: dict(v.split("=") for v in line[1:])} line = {line[0]: dict(v.split("=") for v in line[1:])}
yield line yield line
ret=proc.wait() ret = proc.wait()
if ret!=0: if ret != 0:
exit(ret) exit(ret)
return data return data
def get_streams(path): def get_streams(path):
proc=SP.Popen([ proc = SP.Popen(
"ffprobe", [
"-probesize", str(0x7fffffff), "ffprobe",
"-analyzeduration", str(0x7fffffff), "-probesize",
"-v","fatal", str(0x7FFFFFFF),
"-i",path, "-analyzeduration",
"-select_streams","v:0", str(0x7FFFFFFF),
"-show_streams", "-v",
"-show_format", "fatal",
"-print_format","json" "-i",
],stdout=SP.PIPE,stdin=SP.DEVNULL,bufsize=0) path,
data=json.load(proc.stdout) "-select_streams",
ret=proc.wait() "v:0",
if ret!=0: "-show_streams",
"-show_format",
"-print_format",
"json",
],
stdout=SP.PIPE,
stdin=SP.DEVNULL,
bufsize=0,
)
data = json.load(proc.stdout)
ret = proc.wait()
if ret != 0:
exit(ret) exit(ret)
return data['streams'],data['format'] return data["streams"], data["format"]
def make_header(file): def make_header(file):
return ["DGIndexProjectFile16","1",os.path.abspath(file)] return ["DGIndexProjectFile16", "1", os.path.abspath(file)]
def make_settings(stream): def make_settings(stream):
pict_size="x".join(map(str,[stream["width"],stream["height"]])) pict_size = "x".join(map(str, [stream["width"], stream["height"]]))
frame_rate = list(map(int,stream['r_frame_rate'].split("/"))) frame_rate = list(map(int, stream["r_frame_rate"].split("/")))
frame_rate=(frame_rate[0]*1000)//frame_rate[1] frame_rate = (frame_rate[0] * 1000) // frame_rate[1]
frame_rate=f"{frame_rate} ({stream['r_frame_rate']})" frame_rate = f"{frame_rate} ({stream['r_frame_rate']})"
header=[ header = [
("Stream_Type",0), # Elementary Stream ("Stream_Type", 0), # Elementary Stream
("MPEG_Type",2), # MPEG-2 ("MPEG_Type", 2), # MPEG-2
("iDCT_Algorithm",5), # 64-bit IEEE-1180 Reference ("iDCT_Algorithm", 5), # 64-bit IEEE-1180 Reference
("YUVRGB_Scale",int(stream["color_range"]!="tv")), ("YUVRGB_Scale", int(stream["color_range"] != "tv")),
("Luminance_Filter","0,0"), ("Luminance_Filter", "0,0"),
("Clipping","0,0,0,0"), ("Clipping", "0,0,0,0"),
("Aspect_Ratio",stream["display_aspect_ratio"]), ("Aspect_Ratio", stream["display_aspect_ratio"]),
("Picture_Size",pict_size), ("Picture_Size", pict_size),
("Field_Operation",0), # Honor Pulldown Flags ("Field_Operation", 0), # Honor Pulldown Flags
("Frame_Rate", frame_rate), ("Frame_Rate", frame_rate),
("Location",f"0,0,0,0"), ("Location", "0,0,0,0"),
] ]
for k,v in header: for k, v in header:
yield f"{k}={v}" yield f"{k}={v}"
def gen_d2v(path): def gen_d2v(path):
yield from make_header(path) yield from make_header(path)
yield "" yield ""
streams,fmt=get_streams(path) streams, fmt = get_streams(path)
stream=[s for s in streams if s['codec_type']=='video'][0] stream = [s for s in streams if s["codec_type"] == "video"][0]
stream['index']=str(stream['index']) stream["index"] = str(stream["index"])
yield from make_settings(stream) yield from make_settings(stream)
yield "" yield ""
line_buffer=[] line_buffer = []
frames=get_frames(path) frames = get_frames(path)
prog_bar=tqdm(frames,total=int(fmt['size']),unit_divisor=1024,unit_scale=True,unit="iB",dec="Writing d2v") prog_bar = tqdm(
frames,
total=int(fmt["size"]),
unit_divisor=1024,
unit_scale=True,
unit="iB",
desc="Writing d2v",
)
for line in prog_bar: for line in prog_bar:
if 'frame' not in line: if "frame" not in line:
continue continue
frame=line['frame'] frame = line["frame"]
prog_bar.n=min(max(prog_bar.n,int(frame['pkt_pos'])),int(fmt['size'])) prog_bar.n = min(max(prog_bar.n, int(frame["pkt_pos"])), int(fmt["size"]))
prog_bar.update(0) prog_bar.update(0)
if frame['stream_index']!=stream['index']: if frame["stream_index"] != stream["index"]:
continue continue
if frame['pict_type']=="I" and line_buffer: if frame["pict_type"] == "I" and line_buffer:
yield make_line(line_buffer,stream) yield make_line(line_buffer, stream)
line_buffer.clear() line_buffer.clear()
line_buffer.append(frame) line_buffer.append(frame)
prog_bar.close() prog_bar.close()
yield None yield None
def make_d2v(path): def make_d2v(path):
outfile=os.path.splitext(os.path.basename(path))[0] outfile = os.path.splitext(os.path.basename(path))[0]
outfile=os.path.extsep.join([outfile,"d2v"]) outfile = os.path.extsep.join([outfile, "d2v"])
a,b=ITT.tee(gen_d2v(path)) a, b = ITT.tee(gen_d2v(path))
next(b) next(b)
with open(outfile,"w") as fh: with open(outfile, "w") as fh:
for line,next_line in zip(a,b): for line, next_line in zip(a, b):
fh.write(line) fh.write(line)
if next_line is None: # last line, append end marker if next_line is None: # last line, append end marker
fh.write(" ff") fh.write(" ff")
fh.write("\n") fh.write("\n")

View file

@ -5,59 +5,83 @@ import subprocess as SP
def get_streams(path): def get_streams(path):
proc=SP.Popen([ proc = SP.Popen(
"ffprobe", [
"-probesize", str(0x7fffffff), "ffprobe",
"-analyzeduration", str(0x7fffffff), "-probesize",
"-v","fatal", str(0x7FFFFFFF),
"-i",path, "-analyzeduration",
"-show_streams", str(0x7FFFFFFF),
"-show_format", "-v",
"-print_format","json" "fatal",
],stdout=SP.PIPE,stdin=SP.DEVNULL,bufsize=0) "-i",
data=json.load(proc.stdout) path,
ret=proc.wait() "-show_streams",
if ret!=0: "-show_format",
return [],{} "-print_format",
return data['streams'],data['format'] "json",
],
stdout=SP.PIPE,
stdin=SP.DEVNULL,
bufsize=0,
)
data = json.load(proc.stdout)
ret = proc.wait()
if ret != 0:
return [], {}
return data["streams"], data["format"]
types={
'mpeg2video': 'm2v', types = {
'ac3': 'ac3', "mpeg2video": "m2v",
'dvd_subtitle': 'sup', "ac3": "ac3",
"dvd_subtitle": "sup",
} }
def demux(path): def demux(path):
folder=os.path.dirname(path) folder = os.path.dirname(path)
basename=os.path.splitext(os.path.basename(path))[0] basename = os.path.splitext(os.path.basename(path))[0]
streams,fmt=get_streams(path) streams, fmt = get_streams(path)
cmd=[ cmd = [
"ffmpeg", "ffmpeg",
"-y", "-y",
"-strict","-2", # "-fflags","+genpts+igndts",
"-fflags","+genpts", "-probesize",
"-probesize", str(0x7fffffff), str(0x7FFFFFFF),
"-analyzeduration", str(0x7fffffff), "-analyzeduration",
"-i",path, str(0x7FFFFFFF),
"-scodec","copy", "-i",
"-vcodec","copy", path,
"-acodec","copy", "-strict",
"-2",
"-vcodec",
"copy",
"-acodec",
"copy",
"-scodec",
"copy",
] ]
need_ffmpeg=False need_ffmpeg = False
for stream in streams: for stream in streams:
codec=stream['codec_name'] codec = stream["codec_name"]
ext=types.get(codec,codec) ext = types.get(codec, codec)
idx=stream['index'] idx = stream["index"]
hex_id=stream['id'] hex_id = stream["id"]
codec_name=stream['codec_long_name'] codec_name = stream["codec_long_name"]
outfile=os.path.join(folder,f"{basename}_{idx}_{hex_id}") outfile = os.path.join(folder, f"{basename}_{idx}_{hex_id}")
if codec=="dvd_subtitle": if codec=="dvd_nav_packet":
continue
print(idx, hex_id, codec_name, codec)
if codec == "dvd_subtitle":
SP.check_call([ SP.check_call([
"mencoder",path,"-vobsuboutindex",str(idx),"-vobsubout", outfile,"-nosound","-ovc", "copy", "-o",os.devnull "mencoder",path,"-vobsuboutindex",str(idx),"-vobsubout", outfile,"-nosound","-ovc", "copy", "-o",os.devnull
]) ])
continue continue
print(idx,hex_id,codec_name,codec) cmd += ["-map", f"0:#{hex_id}", "-strict", "-2", outfile + f".{ext}"]
cmd+=["-map",f"0:#{hex_id}",outfile+f".{ext}"] need_ffmpeg = True
need_ffmpeg=True
if need_ffmpeg: if need_ffmpeg:
SP.check_call(cmd) SP.check_call(cmd)
if __name__=="__main__":
demux(sys.argv[1])