Skip to content

Commit

Permalink
Merge pull request #498 from happycube/i495
Browse files Browse the repository at this point in the history
Add some notebook enhancements and work on #493
  • Loading branch information
happycube committed Jun 1, 2020
2 parents d0474eb + e3caf94 commit 9ed93a0
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 144 deletions.
14 changes: 3 additions & 11 deletions ld-decode
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ parser.add_argument('-n', '--ntsc', dest='ntsc', action='store_true', help='sour
parser.add_argument('-m', '--MTF', metavar='mtf', type=float, default=None, help='mtf compensation multiplier')
parser.add_argument('--MTF_offset', metavar='mtf_offset', type=float, default=None, help='mtf compensation offset')
parser.add_argument('-j', '--NTSCJ', dest='ntscj', action='store_true', help='source is in NTSC-J (IRE 0 black) format')
parser.add_argument('--noAGC', dest='noAGC', action='store_true', default=False, help='Disable AGC')
parser.add_argument('--noDOD', dest='nodod', action='store_true', default=False, help='disable dropout detector')
parser.add_argument('--noEFM', dest='noefm', action='store_true', default=False, help='Disable EFM front end')
parser.add_argument('--daa', dest='daa', action='store_true', default=False, help='Disable analog audio decoding')
Expand Down Expand Up @@ -58,7 +59,8 @@ if args.pal and args.ntsc:
print("ERROR: Can only be PAL or NTSC")
exit(1)

extra_options = {}
extra_options = {'useAGC': not args.noAGC}

if args.WibbleRemover:
extra_options['WibbleRemover'] = True
if args.lowband:
Expand Down Expand Up @@ -112,16 +114,6 @@ if len(DecoderParamsOverride.keys()):
if args.verboseVITS:
ldd.verboseVITS = True

def write_json(ldd, outname):
jsondict = ldd.build_json(ldd.curfield)

fp = open(outname + '.tbc.json.tmp', 'w')
json.dump(jsondict, fp, indent=4 if args.verboseVITS else None)
fp.write('\n')
fp.close()

os.rename(outname + '.tbc.json.tmp', outname + '.tbc.json')

done = False

while not done and ldd.fields_written < (req_frames * 2):
Expand Down
34 changes: 20 additions & 14 deletions lddecode/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,8 @@ def setparams(self, params):
def dsa_rescale(infloat):
return int(np.round(infloat * 32767 / 150000))

# right now defualt is 16/48, so not optimal :)
# Downscales to 16bit/44.1khz. It might be nice when analog audio is better to support 24/96,
# but if we only support one output type, matching CD audio/digital sound is greatly preferable.
def downscale_audio(audio, lineinfo, rf, linecount, timeoffset = 0, freq = 48000.0, scale=64):
failed = False

Expand All @@ -1126,8 +1127,7 @@ def downscale_audio(audio, lineinfo, rf, linecount, timeoffset = 0, freq = 48000

# XXX:
# The timing handling can sometimes go outside the bounds of the known line #'s.
# This is a quick-ish fix that should work OK, but may need to be reworked if
# analog audio is weak for some reason
# This is a quick-ish fix that should work OK but may affect quality slightly.
if linenum < 0:
lineloc_cur = int(lineinfo[0] + (rf.linelen * linenum))
lineloc_next = lineloc_cur + rf.linelen
Expand All @@ -1144,7 +1144,7 @@ def downscale_audio(audio, lineinfo, rf, linecount, timeoffset = 0, freq = 48000
sampleloc += (lineloc_next - lineloc_cur) * (linenum - np.floor(linenum))

swow[i] = (lineloc_next - lineloc_cur) / rf.linelen
# There's almost *no way* the disk is spinning more than 5% off, so mask TBC errors here
# There's almost *no way* the disk is spinning more than 1.5% off, so mask TBC errors here
# to reduce pops
if i and np.abs(swow[i] - swow[i - 1]) > .015:
swow[i] = swow[i - 1]
Expand All @@ -1171,8 +1171,6 @@ def downscale_audio(audio, lineinfo, rf, linecount, timeoffset = 0, freq = 48000

failed = True

#print(locs[len(arange)-1], len(audio['audio_left']), np.min(output), np.max(output), swow[len(arange) - 1], linenum)

np.clip(output, -32766, 32766, out=output16)

return output16, arange[-1] - frametime
Expand Down Expand Up @@ -2574,6 +2572,8 @@ def __init__(self, fname_in, fname_out, freader, analog_audio = 0, digital_audio
self.frameNumber = None

self.autoMTF = True
# Python 3.6 doesn't support .get with default=
self.useAGC = extra_options['useAGC'] if 'useAGC' in extra_options else True

self.verboseVITS = False

Expand Down Expand Up @@ -2747,16 +2747,18 @@ def readfield(self, initphase = False):

if not self.checkMTF(f, self.prevfield):
redo = True

sync_hz, ire0_hz = self.detectLevels(f)
sync_ire_diff = np.abs(self.rf.hztoire(sync_hz) - self.rf.SysParams['vsync_ire'])

if (sync_ire_diff > 2) or (np.abs(self.rf.hztoire(ire0_hz)) > 2):
redo = True
# Perform AGC changes on first fields only to prevent luma mismatch intra-field
if self.useAGC and f.isFirstField:
sync_hz, ire0_hz = self.detectLevels(f)
sync_ire_diff = np.abs(self.rf.hztoire(sync_hz) - self.rf.SysParams['vsync_ire'])

if (sync_ire_diff > 2) or (np.abs(self.rf.hztoire(ire0_hz)) > 2):
redo = True

self.rf.SysParams['ire0'] = ire0_hz
# Note that vsync_ire is a negative number, so (sync_hz - ire0_hz) is correct
self.rf.SysParams['hz_ire'] = (sync_hz - ire0_hz) / self.rf.SysParams['vsync_ire']
self.rf.SysParams['ire0'] = ire0_hz
# Note that vsync_ire is a negative number, so (sync_hz - ire0_hz) is correct
self.rf.SysParams['hz_ire'] = (sync_hz - ire0_hz) / self.rf.SysParams['vsync_ire']

if adjusted == False and redo == True:
self.demodcache.flushvideo()
Expand Down Expand Up @@ -3102,6 +3104,8 @@ def seek_getframenr(self, startfield):
f, offset = self.decodefield(initphase = True)

if f is None:
# If given an invalid starting location (i.e. seeking to a frame in an already cut raw file),
# go back to the beginning and try again.
if startfield != 0:
startfield = 0
self.roughseek(startfield)
Expand All @@ -3112,6 +3116,8 @@ def seek_getframenr(self, startfield):
self.curfield = f
self.fdoffset += offset

# Two fields are needed to be sure to have sufficient Philips code data
# to determine frame #.
if self.prevfield is not None and f.valid:
fnum = self.decodeFrameNumber(self.prevfield, self.curfield)

Expand Down
98 changes: 97 additions & 1 deletion lddecode/plot_utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
#!/usr/bin/python3

import io
from io import BytesIO
import re
import subprocess
import sys

import numpy as np
import scipy as sp
import scipy.signal as sps
import sys

import matplotlib.pyplot as plt

# To support image displays
from PIL import Image
import IPython.display
from IPython.display import HTML

pi = np.pi
tau = np.pi * 2

Expand Down Expand Up @@ -130,4 +141,89 @@ def doplot2(B, A, B2, A2, freq = (315.0/88.0) * 8.0):
def BA_to_FFT(B, A, blocklen):
return np.complex64(sps.freqz(B, A, blocklen, whole=True)[1])

# Notebook support functions follow

# Draws a uint16 image, downscaled to uint8
def draw_raw_bwimage(bm, x = 2800, y = 525, hscale = 1, vscale = 2, outsize = None):
if y is None:
y = len(bm) // x

if outsize is None:
outsize = (x * hscale, y * vscale)

bmf = np.uint8(bm[0:x*y] / 256.0)
# print(bmf.shape)
if x is not None:
bms = (bmf.reshape(len(bmf)//x, -1))
else:
bms = bmf

# print(bms.dtype, bms.shape, bms[:][0:y].shape)
im = Image.fromarray(bms[0:y])
im = im.resize(outsize)
b = BytesIO()
im.save(b, format='png')
return IPython.display.Image(b.getvalue())

def draw_field(field):
return draw_raw_bwimage(field.dspicture, field.outlinelen, field.outlinecount)

class RGBoutput:
def __init__(self, outname):
try:
rv = subprocess.run('ld-chroma-decoder {0}.tbc {0}.rgb'.format(outname), capture_output=True, shell=True)
except:

return None

if rv.returncode != 0:
print("Failed to run ld-chroma-decoder: ", rv.returncode)
print(rv.stderr.split('\n'))

stderr = rv.stderr.decode('utf-8')

outres = re.search('trimmed to ([0-9]{3}) x ([0-9]{3})', stderr)
outframes = re.search('complete - ([0-9]*) frames', stderr)

if outres is None or outframes is None:
print("Error, did not decode correctly")

self.x, self.y = [int(v) for v in outres.groups()]
self.numframes = int(outframes.groups()[0])

with open('{0}.rgb'.format(outname), 'rb') as fd:
# 3 colors, 2 bytes/color
raw = fd.read((self.x * self.y * 3 * 2 * self.numframes))

self.rgb = np.frombuffer(raw, 'uint16', len(raw)//2)

def lineslice(self, frame, line):
if line >= self.y or frame > self.numframes:
return None

def getslice(offset):
return slice(((self.y * frame) + line) * (self.x * 3) + offset, ((self.y * frame) + line + 1) * (self.x * 3) + offset, 3)

return [getslice(offset) for offset in range(3)]

def plotline(self, frame, line):
sl = self.lineslice(frame, line)

if sl is None:
return None

plt.plot(self.rgb[sl[0]], 'r')
plt.plot(self.rgb[sl[1]], 'g')
plt.plot(self.rgb[sl[2]], 'b')

def display(self, framenr, scale = 1):
begin = self.lineslice(framenr, 0)[0].start
end = self.lineslice(framenr, self.y - 1)[0].stop

rawimg = self.rgb[begin:end].reshape((self.y, self.x, 3))
rawimg_u8 = np.uint8(rawimg // 256)
im = Image.fromarray(rawimg_u8)
b = BytesIO()
im.save(b, format='png')

return IPython.display.Image(b.getvalue(), width=int(self.x * scale), height=int(self.y * scale))
13 changes: 12 additions & 1 deletion lddecode/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import getopt
import io
from io import BytesIO
import json
import os
import sys
import subprocess
Expand Down Expand Up @@ -797,6 +798,16 @@ def db_to_lev(db):
def lev_to_db(rlev):
return 20 * np.log10(rlev)

# Write the .tbc.json file (used by lddecode and notebooks)
def write_json(ldd, outname):
jsondict = ldd.build_json(ldd.curfield)

fp = open(outname + '.tbc.json.tmp', 'w')
json.dump(jsondict, fp, indent=4 if ldd.verboseVITS else None)
fp.write('\n')
fp.close()

os.rename(outname + '.tbc.json.tmp', outname + '.tbc.json')

if __name__ == "__main__":
print("Nothing to see here, move along ;)")
print("Nothing to see here, move along ;)")
Loading

0 comments on commit 9ed93a0

Please sign in to comment.