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 sys
import time
from dvdnav import DVDNav
from dvdnav import DVDNav,DVDError
from dvdread import DVDRead
import subprocess as SP
import json
from glob import glob
import itertools as ITT
from vob_demux import demux
from ff_d2v import make_d2v
def loadlib(dll_path,*includes,**kwargs):
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)
ffi.cdef(open(include).read(), kwargs)
return ffi, ffi.dlopen(dll_path)
os.environ["DVDCSS_VERBOSE"]="2"
os.environ["DVDCSS_METHOD"]="disc"
for dvd_path in ITT.chain.from_iterable(map(glob,sys.argv[1:])):
r = DVDRead(dvd_path)
# r.grab_ifos()
# r.grab_vobs()
# exit()
r=DVDRead(sys.argv[1])
out_folder=os.path.join("out","_".join([r.disc_id,r.udf_disc_name or r.iso_disc_name]).replace(" ","_"))
del r
os.makedirs(out_folder,exist_ok=True)
d=DVDNav(sys.argv[1])
for k,v in d.titles.items():
v['duration']=v['duration'].total_seconds()
v['chapters']=[c.total_seconds() for c in v['chapters']]
d.titles[k]=v
with open(os.path.join(out_folder,f"{k}.json"),"w") as fh:
json.dump(v,fh)
for a in range(v['angles']):
a+=1
outfile=os.path.join(out_folder,f"{k}_{a}.vob")
with open(outfile,"wb") as fh:
for block in d.get_blocks(k,a):
fh.write(block)
demux(outfile)
os.unlink(outfile)
out_folder = os.path.join(
"out", "_".join([r.disc_id, r.udf_disc_name or r.iso_disc_name]).replace(" ", "_")
)
os.makedirs(out_folder, exist_ok=True)
d = DVDNav(dvd_path)
to_demux = []
for k, v in d.titles.items():
v["duration"] = v["duration"].total_seconds()
v["chapters"] = [c.total_seconds() for c in v["chapters"]]
d.titles[k] = v
with open(os.path.join(out_folder, f"{k:03}.json"), "w") as fh:
json.dump(v, fh)
for a in range(0,99):
block=0
outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob")
to_demux.append(outfile)
fh = open(outfile, "wb")
try:
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)

315
dvdnav.py
View file

@ -3,58 +3,77 @@ import os
import functools
from datetime import timedelta
from tqdm import tqdm
from dvdread import DVDRead
def loadlib(dll_path,*includes,**kwargs):
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)
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",
def __init__(self, path, verbose=None, method="disc"):
if verbose is None:
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(
"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={}
"dvdnav.h",
pack=True,
)
self.path = path
self.titles = {}
self.open(path)
def __del__(self):
self.__check_error(self.lib.dvdnav_close(self.dvd))
self.dvd=None
self.dvd = None
def __repr__(self):
return "<DVD Path={0.path} Title={0.title} Serial={0.serial}".format(self)
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_title_play(self.dvd,title))
self.__check_error(self.lib.dvdnav_angle_change(self.dvd,angle))
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_title_play(self.dvd, title))
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:
self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd,slang))
event=self.lib.DVDNAV_NOP
buf=self.ffi.new("char[]",4096)
ev=self.ffi.new("int32_t*",self.lib.DVDNAV_NOP)
size=self.ffi.new("int32_t*",0)
pos=self.ffi.new("uint32_t*",0)
total_size=self.ffi.new("uint32_t*",0)
self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd, slang))
event = self.lib.DVDNAV_NOP
buf = self.ffi.new("char[]", 4096)
ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP)
size = self.ffi.new("int32_t*", 0)
pos = self.ffi.new("uint32_t*", 0)
total_size = self.ffi.new("uint32_t*", 0)
domains = {
1: "FirstPlay",
2: "VTSTitle",
4: "VMGM",
8: "VTSMenu"
8: "VTSMenu",
}
events={
events = {
0: "DVDNAV_BLOCK_OK",
1: "DVDNAV_NOP",
2: "DVDNAV_STILL_FRAME",
@ -67,146 +86,180 @@ class DVDNav(object):
9: "DVDNAV_HIGHLIGHT",
10: "DVDNAV_SPU_CLUT_CHANGE",
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:
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:
progbar.total=total_size[0]*2048
progbar.n=max(progbar.n,min(progbar.total,pos[0]*2048))
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
):
progbar.total = total_size[0] * 2048
progbar.n = max(progbar.n, min(progbar.total, pos[0] * 2048))
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])
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
elif ev[0]==self.lib.DVDNAV_BLOCK_OK:
# print("Read",size[0])
yield self.ffi.buffer(buf,size[0])[:]
elif ev[0]==self.lib.DVDNAV_STOP:
elif ev[0] == self.lib.DVDNAV_BLOCK_OK:
yield self.ffi.buffer(buf, size[0])[:]
elif ev[0] == self.lib.DVDNAV_STOP:
progbar.write(f"[{title}|{angle}] Stop")
break
elif ev[0]==self.lib.DVDNAV_NAV_PACKET:
nav=self.lib.dvdnav_get_current_nav_pci(self.dvd)
# print("PTS:",timedelta(seconds=nav.pci_gi.vobu_s_ptm/90000))
elif ev[0]==self.lib.DVDNAV_STILL_FRAME:
# print("Still")
elif ev[0] == self.lib.DVDNAV_NAV_PACKET:
pass
elif ev[0] == self.lib.DVDNAV_STILL_FRAME:
self.__check_error(self.lib.dvdnav_still_skip(self.dvd))
elif ev[0]==self.lib.DVDNAV_WAIT:
# print("Wait",size[0])
elif ev[0] == self.lib.DVDNAV_WAIT:
self.__check_error(self.lib.dvdnav_wait_skip(self.dvd))
elif ev[0]==self.lib.DVDNAV_SPU_STREAM_CHANGE:
spu=self.ffi.cast("dvdnav_spu_stream_change_event_t*",buf)
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:
audio=self.ffi.cast("dvdnav_audio_stream_change_event_t*",buf)
progbar.write(f"[{title}|{angle}] Audio: Physical: {audio.physical} Logical: {audio.logical}")
elif ev[0]==self.lib.DVDNAV_CELL_CHANGE:
cell=self.ffi.cast("dvdnav_cell_change_event_t*",buf)
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})")
elif ev[0]==self.lib.DVDNAV_VTS_CHANGE:
vts=self.ffi.cast("dvdnav_vts_change_event_t*",buf)
old_domain=sorted(domains[k] for k in domains if vts.old_domain&k)
new_domain=sorted(domains[k] for k in domains if vts.new_domain&k)
progbar.write(f"[{title}|{angle}] VTS: {vts.old_vtsN} ({vts.old_domain} {old_domain}) -> {vts.new_vtsN} ({vts.new_domain} {new_domain})")
if vts.new_domain==8: # back to menu
elif ev[0] == self.lib.DVDNAV_SPU_STREAM_CHANGE:
pass
elif ev[0] == self.lib.DVDNAV_AUDIO_STREAM_CHANGE:
audio = self.ffi.cast("dvdnav_audio_stream_change_event_t*", buf)
elif ev[0] == self.lib.DVDNAV_CELL_CHANGE:
cell = self.ffi.cast("dvdnav_cell_change_event_t*", buf)
current_cell = cell.cellN
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})"
)
elif ev[0] == self.lib.DVDNAV_VTS_CHANGE:
vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf)
new_vts = (vts.new_vtsN, vts.new_domain)
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
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:
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):
if ret==self.lib.DVDNAV_STATUS_ERR:
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))
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:
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:
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=[]
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
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
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))
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']={}
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:
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
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={
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',
1: "normal",
2: "descriptive",
3: "director's commentary",
4: "alternate 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
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:
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))
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
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))
self.__check_error(self.lib.dvdnav_stop(self.dvd))

View file

@ -4,16 +4,24 @@ import functools
import binascii
from datetime import timedelta
def loadlib(dll_path,*includes,**kwargs):
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)
ffi.cdef(open(include).read(), **kwargs)
return ffi, ffi.dlopen(dll_path)
class DVDRead(object):
def __init__(self,path):
self.dvd=None
self.ffi,self.lib = loadlib("libdvdread-8.dll",
def __init__(self, path, verbose="0", method="disc"):
if verbose is None:
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_reader.h",
"ifo_types.h",
@ -21,23 +29,137 @@ class DVDRead(object):
"ifo_print.h",
"nav_types.h",
"nav_read.h",
"nav_print.h")
self.path=path
self.titles={}
"nav_print.h",
packed=True,
)
self.path = path
self.titles = {}
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):
if 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=self.lib.DVDOpen(bytes(path,"utf8"))
vol_id=self.ffi.new("unsigned char[]",32)
self.lib.DVDDiscID(self.dvd,vol_id)
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.udf_disc_name=str(self.ffi.string(vol_id),"utf8")
self.lib.DVDISOVolumeInfo(self.dvd,vol_id,32,self.ffi.NULL,0)
self.iso_disc_name=str(self.ffi.string(vol_id),"utf8")
self.ffi.release(vol_id)
self.dvd = self.lib.DVDOpen(bytes(path, "utf8"))
vol_id = self.ffi.new("unsigned char[]", 32)
self.lib.DVDDiscID(self.dvd, vol_id)
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.udf_disc_name = str(self.ffi.string(vol_id), "utf8")
self.lib.DVDISOVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0)
self.iso_disc_name = str(self.ffi.string(vol_id), "utf8")
self.ffi.release(vol_id)

236
ff_d2v.py
View file

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

View file

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