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")