174 lines
4.9 KiB
Python
174 lines
4.9 KiB
Python
|
import sys
|
||
|
import json
|
||
|
import os
|
||
|
import subprocess as SP
|
||
|
import itertools as ITT
|
||
|
from tqdm import tqdm
|
||
|
|
||
|
|
||
|
colorspace={
|
||
|
"gbr": 0,
|
||
|
"bt709": 1,
|
||
|
"unknown": 2,
|
||
|
"fcc": 4,
|
||
|
"bt470bg": 5,
|
||
|
"smpte170m": 6,
|
||
|
"smpte240m": 7,
|
||
|
"ycgco": 8,
|
||
|
"bt2020nc": 9,
|
||
|
"bt2020c": 10,
|
||
|
"smpte2085": 11,
|
||
|
"chroma-derived-nc": 12,
|
||
|
"chroma-derived-c": 13,
|
||
|
"ictcp": 14,
|
||
|
}
|
||
|
|
||
|
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
|
||
|
return info
|
||
|
|
||
|
def make_flags(frames):
|
||
|
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
|
||
|
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 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
|
||
|
for line in proc.stdout:
|
||
|
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:
|
||
|
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:
|
||
|
exit(ret)
|
||
|
return data['streams'],data['format']
|
||
|
|
||
|
def make_header(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
|
||
|
("Frame_Rate", frame_rate),
|
||
|
("Location",f"0,0,0,0"),
|
||
|
]
|
||
|
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'])
|
||
|
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")
|
||
|
for line in prog_bar:
|
||
|
if 'frame' not in line:
|
||
|
continue
|
||
|
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']:
|
||
|
continue
|
||
|
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))
|
||
|
next(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
|
||
|
fh.write(" ff")
|
||
|
fh.write("\n")
|