-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathhacktv-to-tbc
executable file
·161 lines (146 loc) · 5.47 KB
/
hacktv-to-tbc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/usr/bin/python3
# Run hacktv, converting its output into a simulated .tbc file.
# Changes this needs to make:
# - Scale hacktv's black/white levels to match ld-decode's
# - Pad hacktv's 1135x625 frame to match ld-decode's 1135x626 frame
# Usage: hacktv-to-tbc fake.tbc input.avi
# XXX NTSC colours aren't correct - sample rate?
import json
import numpy
import optparse
import subprocess
import sys
# Parse command-line options
parser = optparse.OptionParser(usage="usage: %prog [options] TBC-FILE [HACKTV-ARGS ...]")
parser.disable_interspersed_args()
parser.add_option("-l", dest="length", metavar="FRAMES",
help="maximum number of frames to process")
parser.add_option("-n", "--ntsc", dest="standard", action="store_const", const="ntsc",
help="generate NTSC output")
parser.add_option("-p", "--pal", dest="standard", action="store_const", const="pal",
help="generate PAL output")
options, args = parser.parse_args(sys.argv[1:])
max_length = -1
if options.length:
max_length = int(options.length)
if options.standard is None:
parser.error("you must specify either --pal or --ntsc")
if options.standard == "pal":
# Parameters we're trying to generate (copied from ld-decode output)
videoParameters = {
"activeVideoEnd": 1107.0,
"activeVideoStart": 185.0,
"black16bIre": 16384.0,
"colourBurstEnd": 138.0,
"colourBurstStart": 98.0,
"fieldHeight": 313,
"fieldWidth": 1135,
"fsc": 4433618,
"isSourcePal": True,
"numberOfSequentialFields": 0,
# XXX To match real ld-decode, this should be:
# "sampleRate": 17734472,
# but hacktv adjusts the sample rate internally.
"sampleRate": 17734375,
"white16bIre": 54016.0,
}
# Parameters from vid_config_pal in hacktv's video.c (where they differ from the above)
hacktv_lines = 625
hacktv_white_level = 0.70
hacktv_black_level = 0.00
hacktv_sync_level = -0.30
hacktv_burst_level = 3.0 / 7.0
else:
# Parameters we're trying to generate (copied from ld-decode output)
videoParameters = {
"activeVideoEnd": 887.0,
"activeVideoStart": 134.0,
"black16bIre": 18048.0,
"colourBurstEnd": 110.0,
"colourBurstStart": 74.0,
"fieldHeight": 263,
"fieldWidth": 910,
"fsc": 3579545,
"isSourcePal": False,
"numberOfSequentialFields": 0,
# XXX Should be: "sampleRate": 14318180,
"sampleRate": 14318181.818182,
"white16bIre": 51200.0,
}
# Parameters from vid_config_ntsc in hacktv's video.c (where they differ from the above)
hacktv_lines = 525
hacktv_white_level = 0.70
hacktv_black_level = 0.0525
hacktv_sync_level = -0.30
hacktv_burst_level = 4.0 / 10.0
# Dummy black line to pad the output with.
# (In a real .tbc this is a copy of the first line of the next field...)
black_line = numpy.full(videoParameters["fieldWidth"],
videoParameters["black16bIre"],
dtype=numpy.uint16)
# First positional arg is TBC filename
if len(args) < 1:
parser.error("no output filename specified")
filename_tbc = args[0]
filename_json = filename_tbc + ".json"
# Remaining positional args are forwarded to hacktv
hacktv_cmd = [
"hacktv",
"-o", "-",
"-m", options.standard,
"-s", str(videoParameters["sampleRate"]),
]
hacktv_cmd += args[1:]
hacktv = subprocess.Popen(hacktv_cmd, stdout=subprocess.PIPE)
# Convert the video
fields = []
numFields = 0
with open(filename_tbc, "wb") as f:
while True:
# Read two fields from hacktv
rawsize = videoParameters["fieldWidth"] * hacktv_lines * 2
rawdata = hacktv.stdout.read(rawsize)
if len(rawdata) < rawsize:
# End of file
break
data = numpy.frombuffer(rawdata, numpy.int16).astype(float)
# Scale to match the levels in videoParameters
data -= hacktv_black_level * 32767
data /= hacktv_white_level * 32767
data *= (videoParameters["white16bIre"] - videoParameters["black16bIre"])
data += videoParameters["black16bIre"]
data = numpy.clip(data, 0, 65535)
# Write to the output, padded to the right height
data.astype(numpy.uint16).tofile(f)
for i in range((2 * videoParameters["fieldHeight"]) - hacktv_lines):
black_line.tofile(f)
# Generate JSON info for fields
for i in range(2):
numFields += 1
field = {
"isFirstField": i == 0,
"syncConf": 100,
"seqNo": numFields,
"diskLoc": numFields,
# This value is sqrt(2) * RMS(samples in burst), i.e. half the P-P amplitude
"medianBurstIRE": 100.0 * hacktv_burst_level / 2,
"decodeFaults": 0,
"vitsMetrics": {"wSNR": 42.0, "bPSNR": 42.0},
"vbi": {"vbiData": [42, 42, 42]},
"audioSamples": 42,
}
if options.standard == "ntsc":
field["fieldPhaseID"] = ((numFields - 1) % 4) + 1
fields.append(field)
# Limit output length
if max_length > 0 and (numFields / 2) >= max_length:
break
hacktv.stdout.close()
hacktv.wait()
# Write the JSON
videoParameters["numberOfSequentialFields"] = numFields
with open(filename_json, "w") as f:
json.dump({
"videoParameters": videoParameters,
"fields": fields,
}, f)