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)
to_demux = []
for k, v in d.titles.items(): for k, v in d.titles.items():
v['duration']=v['duration'].total_seconds() v["duration"] = v["duration"].total_seconds()
v['chapters']=[c.total_seconds() for c in v['chapters']] v["chapters"] = [c.total_seconds() for c in v["chapters"]]
d.titles[k] = v d.titles[k] = v
with open(os.path.join(out_folder,f"{k}.json"),"w") as fh: with open(os.path.join(out_folder, f"{k:03}.json"), "w") as fh:
json.dump(v, fh) json.dump(v, fh)
for a in range(v['angles']): for a in range(0,99):
a+=1 block=0
outfile=os.path.join(out_folder,f"{k}_{a}.vob") outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob")
with open(outfile,"wb") as fh: to_demux.append(outfile)
fh = open(outfile, "wb")
try:
for block in d.get_blocks(k, a): 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) fh.write(block)
demux(outfile) except DVDError as e:
os.unlink(outfile) 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)

159
dvdnav.py
View file

@ -3,6 +3,8 @@ 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()
@ -10,21 +12,29 @@ def loadlib(dll_path,*includes,**kwargs):
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"):
if verbose is None:
os.environ.pop("DVDCSS_VERBOSE", None)
else:
os.environ["DVDCSS_VERBOSE"] = str(verbose) os.environ["DVDCSS_VERBOSE"] = str(verbose)
os.environ["DVDCSS_METHOD"] = method os.environ["DVDCSS_METHOD"] = method
self.dvd = None self.dvd = None
self.ffi,self.lib = loadlib("libdvdnav-4.dll", 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",
pack=True,
)
self.path = path self.path = path
self.titles = {} self.titles = {}
self.open(path) self.open(path)
@ -39,6 +49,15 @@ class DVDNav(object):
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))
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)) 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))
@ -52,7 +71,7 @@ class DVDNav(object):
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",
@ -67,51 +86,83 @@ 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 (
self.lib.dvdnav_get_position(self.dvd, pos, total_size)
== self.lib.DVDNAV_STATUS_OK
):
progbar.total = total_size[0] * 2048 progbar.total = total_size[0] * 2048
progbar.n = max(progbar.n, min(progbar.total, pos[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)
progbar.write(f"[{title}|{angle}] Audio: Physical: {audio.physical} Logical: {audio.logical}")
elif ev[0] == self.lib.DVDNAV_CELL_CHANGE: elif ev[0] == self.lib.DVDNAV_CELL_CHANGE:
cell = self.ffi.cast("dvdnav_cell_change_event_t*", buf) 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})") 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: elif ev[0] == self.lib.DVDNAV_VTS_CHANGE:
vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf) 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_vts = (vts.new_vtsN, vts.new_domain)
new_domain=sorted(domains[k] for k in domains if vts.new_domain&k) 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})") # 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 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:
@ -134,17 +185,19 @@ class DVDNav(object):
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(
self.dvd, title, times, duration
)
if duration[0] == 0: 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):
@ -165,48 +218,48 @@ class DVDNav(object):
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(
self.lib.dvdnav_get_audio_attr(self.dvd, stream_id, audio_attrs)
)
alang = None 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',
4: 'lpcm',
6: 'dts'
}[audio_attrs.audio_format]
audio_type = { 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"):
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.dvd = None
self.ffi,self.lib = loadlib("libdvdread-8.dll", 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,10 +29,124 @@ 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",
packed=True,
)
self.path = path self.path = path
self.titles = {} 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:

112
ff_d2v.py
View file

@ -23,15 +23,12 @@ 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
@ -39,15 +36,16 @@ def make_info(frames):
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
@ -61,9 +59,9 @@ def make_flags(frames):
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
@ -72,16 +70,27 @@ def make_line(frames,stream):
def get_frames(path): def get_frames(path):
proc=SP.Popen([ proc = SP.Popen(
[
"ffprobe", "ffprobe",
"-probesize", str(0x7fffffff), "-probesize",
"-analyzeduration", str(0x7fffffff), str(0x7FFFFFFF),
"-v","fatal", "-analyzeduration",
"-i",path, str(0x7FFFFFFF),
"-select_streams","v:0", "-v",
"fatal",
"-i",
path,
"-select_streams",
"v:0",
"-show_frames", "-show_frames",
"-print_format","compact" "-print_format",
],stdout=SP.PIPE,stdin=SP.DEVNULL,bufsize=0) "compact",
],
stdout=SP.PIPE,
stdin=SP.DEVNULL,
bufsize=0,
)
data = None data = None
for line in proc.stdout: for line in proc.stdout:
line = str(line, "utf8").strip().split("|") line = str(line, "utf8").strip().split("|")
@ -92,30 +101,44 @@ def get_frames(path):
exit(ret) exit(ret)
return data return data
def get_streams(path): def get_streams(path):
proc=SP.Popen([ proc = SP.Popen(
[
"ffprobe", "ffprobe",
"-probesize", str(0x7fffffff), "-probesize",
"-analyzeduration", str(0x7fffffff), str(0x7FFFFFFF),
"-v","fatal", "-analyzeduration",
"-i",path, str(0x7FFFFFFF),
"-select_streams","v:0", "-v",
"fatal",
"-i",
path,
"-select_streams",
"v:0",
"-show_streams", "-show_streams",
"-show_format", "-show_format",
"-print_format","json" "-print_format",
],stdout=SP.PIPE,stdin=SP.DEVNULL,bufsize=0) "json",
],
stdout=SP.PIPE,
stdin=SP.DEVNULL,
bufsize=0,
)
data = json.load(proc.stdout) data = json.load(proc.stdout)
ret = proc.wait() ret = proc.wait()
if ret != 0: 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 = [
@ -129,37 +152,46 @@ def make_settings(stream):
("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"])

View file

@ -5,28 +5,40 @@ import subprocess as SP
def get_streams(path): def get_streams(path):
proc=SP.Popen([ proc = SP.Popen(
[
"ffprobe", "ffprobe",
"-probesize", str(0x7fffffff), "-probesize",
"-analyzeduration", str(0x7fffffff), str(0x7FFFFFFF),
"-v","fatal", "-analyzeduration",
"-i",path, str(0x7FFFFFFF),
"-v",
"fatal",
"-i",
path,
"-show_streams", "-show_streams",
"-show_format", "-show_format",
"-print_format","json" "-print_format",
],stdout=SP.PIPE,stdin=SP.DEVNULL,bufsize=0) "json",
],
stdout=SP.PIPE,
stdin=SP.DEVNULL,
bufsize=0,
)
data = json.load(proc.stdout) data = json.load(proc.stdout)
ret = proc.wait() ret = proc.wait()
if ret != 0: if ret != 0:
return [], {} return [], {}
return data['streams'],data['format'] return data["streams"], data["format"]
types = { types = {
'mpeg2video': 'm2v', "mpeg2video": "m2v",
'ac3': 'ac3', "ac3": "ac3",
'dvd_subtitle': 'sup', "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]
@ -34,30 +46,42 @@ def demux(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_nav_packet":
continue
print(idx, hex_id, codec_name, codec)
if codec == "dvd_subtitle": 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])