Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RF TBC .ldf output #586

Merged
merged 2 commits into from
Dec 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions ld-cut
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,7 @@ if makelds:
process = subprocess.Popen(['ld-lds-converter', '-o', outname, '-p'], stdin=subprocess.PIPE)
fd = process.stdin
elif makeldf:
corecmd = "ffmpeg -y -hide_banner -loglevel error -f s16le -ar 40k -ac 1 -i - -acodec flac -f ogg".split(' ')
process = subprocess.Popen([*corecmd, '-compression_level', str(args.ldfcomp), outname], stdin=subprocess.PIPE)
fd = process.stdin
process, fd = ldf_pipe(outname, args.ldfcomp)
else:
fd = open(args.outfile, 'wb')

Expand Down
17 changes: 14 additions & 3 deletions ld-decode
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,24 @@ parser.add_argument('--daa', dest='daa', action='store_true', default=False, hel
parser.add_argument('--ignoreleadout', dest='ignoreleadout', action='store_true', default=False, help='continue decoding after lead-out seen')
parser.add_argument('--verboseVITS', dest='verboseVITS', action='store_true', default=False, help='Enable additional JSON fields')

parser.add_argument('--RF_TBC', dest='RF_TBC', action='store_true', default=False, help="Create a .tbc.ldf file with TBC'd RF")

parser.add_argument('--lowband', dest='lowband', action='store_true', default=False, help='Use more restricted RF settings for noisier disks')

parser.add_argument('--NTSC_color_notch_filter', dest='NTSC_color_notch_filter', action='store_true', default=False, help='Mitigate interference from analog audio in reds in NTSC captures')
parser.add_argument('--V4300D_notch_filter', dest='V4300D_notch_filter', action='store_true', default=False, help='LD-V4300D PAL/digital audio captures: remove spurious ~8.5mhz signal')

parser.add_argument('-d', '--deemp_adjust', metavar='deemp_adjust', type=float, default=1.0, help='mtf compensation multiplier')
parser.add_argument('-d', '--deemp_adjust', metavar='deemp_adjust', type=float, default=1.0, help='Deemphasis level multiplier')
parser.add_argument('--deemp_low', metavar='deemp_low', type=float, default=0, help='Deemphasis low coefficient')
parser.add_argument('--deemp_high', metavar='deemp_high', type=float, default=0, help='Deemphasis high coefficient')

parser.add_argument('-t', '--threads', metavar='threads', type=int, default=5, help='number of CPU threads to use')

parser.add_argument('-f', '--frequency', dest='inputfreq', metavar='FREQ', type=parse_frequency, default=None, help='RF sampling frequency in source file (default is 40MHz)')

parser.add_argument('--video_bpf_high', dest='vbpf_high', metavar='FREQ', type=parse_frequency, default=None, help='Video BPF high end frequency')
parser.add_argument('--video_lpf', dest='vlpf', metavar='FREQ', type=parse_frequency, default=None, help='Video low-pass filter frequency')

parser.add_argument('--video_lpf_order', dest='vlpf_order', type=int, default=-1, help='Video low-pass filter order')

args = parser.parse_args()
#print(args)
Expand All @@ -69,7 +74,10 @@ if args.pal and args.ntsc:
print("ERROR: Can only be PAL or NTSC")
exit(1)

extra_options = {'useAGC': not args.noAGC, 'deemp_level': (args.deemp_adjust, args.deemp_adjust)}
extra_options = {'useAGC': not args.noAGC,
'write_RF_TBC': args.RF_TBC,
'deemp_mult': (args.deemp_adjust, args.deemp_adjust),
'deemp_coeff': (args.deemp_low, args.deemp_high)}

if vid_standard == 'NTSC' and args.NTSC_color_notch_filter:
extra_options['NTSC_ColorNotchFilter'] = True
Expand Down Expand Up @@ -125,6 +133,9 @@ if args.vbpf_high is not None:
if args.vlpf is not None:
DecoderParamsOverride['video_lpf_freq'] = args.vlpf * 1000000

if args.vlpf_order >= 1:
DecoderParamsOverride['video_lpf_order'] = args.vlpf_order

if len(DecoderParamsOverride.keys()):
ldd.demodcache.setparams(DecoderParamsOverride)

Expand Down
86 changes: 70 additions & 16 deletions lddecode/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,17 @@ def __init__(self, inputfreq = 40, system = 'NTSC', blocklen = 32*1024, decode_d

self.SysParams['analog_audio'] = has_analog_audio

self.deemp_level = extra_options.get('deemp_level')
if self.deemp_level is None:
self.deemp_level = (1.0, 1.0)
self.deemp_mult = extra_options.get('deemp_mult', (1.0, 1.0))

deemp = list(self.DecoderParams['video_deemp'])

if extra_options.get('deemp_low', 0):
deemp[0] = extra_options.get['deemp_low']

if extra_options.get('deemp_high', 0):
deemp[1] = extra_options.get['deemp_high']

self.DecoderParams['video_deemp'] = deemp

linelen = self.freq_hz/(1000000.0/self.SysParams['line_period'])
self.linelen = int(np.round(linelen))
Expand Down Expand Up @@ -480,6 +488,9 @@ def computevideofilters(self):
video_lpf = sps.butter(DP['video_lpf_order'], DP['video_lpf_freq']/self.freq_hz_half, 'low')
SF['Fvideo_lpf'] = filtfft(video_lpf, self.blocklen)

video_bpf = sps.butter(2, [3900000/self.freq_hz_half, 4500000/self.freq_hz_half], 'bandpass')
SF['Fvideo_bpf'] = filtfft(video_bpf, self.blocklen)

if self.system == 'NTSC' and self.NTSC_ColorNotchFilter:
video_notch = sps.butter(3, [DP['video_lpf_freq']/1000000/self.freq_half, 5.0/self.freq_half], 'bandstop')
SF['Fvideo_lpf'] *= filtfft(video_notch, self.blocklen)
Expand All @@ -489,16 +500,16 @@ def computevideofilters(self):

# The deemphasis filter
deemp1, deemp2 = DP['video_deemp']
deemp1 *= self.deemp_level[0]
deemp2 *= self.deemp_level[1]
deemp1 *= self.deemp_mult[0]
deemp2 *= self.deemp_mult[1]
SF['Fdeemp'] = filtfft(emphasis_iir(deemp1, deemp2, self.freq_hz), self.blocklen)

# The direct opposite of the above, used in test signal generation
SF['Femp'] = filtfft(emphasis_iir(deemp2, deemp1, self.freq_hz), self.blocklen)

# Post processing: lowpass filter + deemp
SF['FVideo'] = SF['Fvideo_lpf'] * SF['Fdeemp']
SF['FVideo'] = (SF['Fvideo_lpf'] * SF['Fdeemp']) #+ (SF['Fvideo_bpf'] / 10)

# additional filters: 0.5mhz and color burst
# Using an FIR filter here to get a known delay
F0_5 = sps.firwin(65, [0.5/self.freq_half], pass_zero=True)
Expand Down Expand Up @@ -2036,6 +2047,37 @@ def downscale(self, lineinfo = None, linesout = None, outwidth = None, channel='

return dsout, self.dsaudio, self.efmout

def rf_tbc(self, linelocs = None):
''' This outputs a TBC'd version of the input RF data, mostly intended
to assist in audio processing. Outputs a uint16 array.
'''

# Convert raw RF to floating point to help the scaler
fdata = self.data['input'].astype(np.float)

if linelocs is None:
linelocs = self.linelocs

# Ensure that the output line length is an integer
linelen = int(round(self.inlinelen))

# Adjust for the demodulation/filtering delays
delay = self.rf.delays['video_white']

# For output consistency reasons, linecount is set to 313 (i.e. 626 lines)
# in PAL mode. This needs to be corrected for RF TBC.
lc = self.linecount
if self.rf.system == 'PAL' and not self.isFirstField:
lc = 312

output = []

for l in range(self.lineoffset, self.lineoffset + lc):
scaled = scale(fdata, linelocs[l] - delay, linelocs[l + 1] - delay, linelen)
output.append(np.round(scaled).astype(np.int16))

return np.concatenate(output)

def decodephillipscode(self, linenum):
linestart = self.linelocs[linenum]
data = self.data['video']['demod']
Expand Down Expand Up @@ -2721,6 +2763,7 @@ def __init__(self, fname_in, fname_out, freader, _logger, est_frames = None, ana

self.analog_audio = int(analog_audio * 1000)
self.digital_audio = digital_audio
self.write_rf_tbc = extra_options.get('write_RF_TBC', False)

self.has_analog_audio = True
if system == 'PAL':
Expand All @@ -2731,18 +2774,21 @@ def __init__(self, fname_in, fname_out, freader, _logger, est_frames = None, ana

self.lastvalidfield = {False: None, True: None}

self.outfile_video = None
self.outfile_audio = None
self.outfile_efm = None
self.ffmpeg_rftbc, self.outfile_rftbc = None, None

if fname_out is not None:
self.outfile_video = open(fname_out + '.tbc', 'wb')
self.outfile_audio = open(fname_out + '.pcm', 'wb') if self.analog_audio else None
self.outfile_efm = open(fname_out + '.efm', 'wb') if self.digital_audio else None

if digital_audio:
if self.analog_audio:
self.outfile_audio = open(fname_out + '.pcm', 'wb')
if self.digital_audio:
# feed EFM stream into ld-ldstoefm
self.efm_pll = efm_pll.EFM_PLL()
else:
self.outfile_video = None
self.outfile_audio = None
self.outfile_efm = None
self.outfile_efm = open(fname_out + '.efm', 'wb')
if self.write_rf_tbc:
self.ffmpeg_rftbc, self.outfile_rftbc = ldf_pipe(fname_out + '.tbc.ldf')

self.fname_out = fname_out

Expand Down Expand Up @@ -2802,8 +2848,13 @@ def __del__(self):
def close(self):
''' deletes all open files, so it's possible to pickle an LDDecode object '''

try:
self.ffmpeg_rftbc.kill()
except:
pass

# use setattr to force file closure by unlinking the objects
for outfiles in ['infile', 'outfile_video', 'outfile_audio', 'outfile_json', 'outfile_efm']:
for outfiles in ['infile', 'outfile_video', 'outfile_audio', 'outfile_json', 'outfile_efm', 'outfile_rftbc']:
setattr(self, outfiles, None)

self.demodcache.end()
Expand Down Expand Up @@ -2869,6 +2920,9 @@ def writeout(self, dataset):
self.outfile_video.write(picture)
self.fields_written += 1

if self.outfile_rftbc is not None:
self.outfile_rftbc.write(f.rf_tbc())

if audio is not None and self.outfile_audio is not None:
self.outfile_audio.write(audio)

Expand Down
6 changes: 6 additions & 0 deletions lddecode/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,12 @@ def read(self, infile, sample, readlen):
def __call__(self, infile, sample, readlen):
return self.read(infile, sample, readlen)

def ldf_pipe(outname, compression_level = 6):
corecmd = "ffmpeg -y -hide_banner -loglevel error -f s16le -ar 40k -ac 1 -i - -acodec flac -f ogg".split(' ')
process = subprocess.Popen([*corecmd, '-compression_level', str(compression_level), outname], stdin=subprocess.PIPE)

return process, process.stdin

# Git helpers

def get_git_info():
Expand Down