Skip to content

Commit 7c83e05

Browse files
committed
Make frame index assert report more information, and attempt to fix it
1 parent d73334d commit 7c83e05

File tree

2 files changed

+65
-62
lines changed

2 files changed

+65
-62
lines changed

Diff for: stammer.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def tesselate_composite(match_row, basis_coefficients, i):
107107
used_coeffs = [(j, coefficient) for j, coefficient in enumerate(basis_coefficients) if coefficient != 0]
108108
for k, coeff in used_coeffs:
109109
frame_num = min(match_row[k], video_handler.framecount - 1)
110-
tiles.append(Image.open(video_handler.get_frame(frame_num+1)))
110+
tiles.append(Image.open(video_handler.get_frame(frame_num)))
111111
hot_bits,_ = fraction_bits.as_array(coeff)
112112
bits.append(hot_bits)
113113
tesselation = image_tiling.Tiling(height=tiles[0].height,width=tiles[0].width)
@@ -133,19 +133,19 @@ def tesselate_composite(match_row, basis_coefficients, i):
133133
best_matches = matcher.get_best_matches()
134134

135135
if type(matcher) in (BasicAudioMatcher, UniqueAudioMatcher):
136-
for video_frame_i in range(int(len(best_matches) * audio_frame_length / video_frame_length)):
136+
for video_frame_i in range(video_handler.best_match_count):
137137
elapsed_time = video_frame_i * video_frame_length
138138
audio_frame_i = int(elapsed_time / audio_frame_length)
139139
time_past_start_of_audio_frame = elapsed_time - (audio_frame_i * audio_frame_length)
140140
match_num = best_matches[audio_frame_i]
141141
elapsed_time_in_carrier = match_num * audio_frame_length + time_past_start_of_audio_frame
142142
carrier_video_frame = int(elapsed_time_in_carrier / video_frame_length)
143143
carrier_video_frame = min(carrier_video_frame, int(video_handler.framecount - 1))
144-
video_handler.write_frame(video_frame_i,video_handler.get_frame(carrier_video_frame+1))
144+
video_handler.write_frame(video_frame_i,video_handler.get_frame(carrier_video_frame))
145145

146146
elif type(matcher) == CombinedFrameAudioMatcher:
147147
basis_coefficients = matcher.get_basis_coefficients()
148-
for video_frame_i in range(int(len(best_matches) * audio_frame_length / video_frame_length)):
148+
for video_frame_i in range(video_handler.best_match_count):
149149
elapsed_time = video_frame_i * video_frame_length
150150
audio_frame_i = int(elapsed_time / audio_frame_length)
151151
time_past_start_of_audio_frame = elapsed_time - (audio_frame_i * audio_frame_length)
@@ -259,8 +259,6 @@ def process(carrier_path, modulator_path, output_path, custom_frame_length, matc
259259
handler.set_min_cached_frames(min_cached_frames)
260260
elif video_mode == "disk":
261261
handler = VideoHandlerDisk(carrier_path,output_path,TEMP_DIR,matcher,carrier_framecount,video_frame_length,color_mode)
262-
outframes_dir = TEMP_DIR / 'outframes'
263-
outframes_dir.mkdir()
264262

265263
build_output_video(handler, matcher)
266264
else:

Diff for: video_out.py

+61-56
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,29 @@ def __init__(self, carrier_path: Path, output_path: Path, temp_dir: Path, matche
2727
self.carrier_path = carrier_path
2828
self.output_path = output_path
2929
self.temp_dir = temp_dir
30-
self.frames_dir = self.temp_dir / 'frames'
31-
self.outframes_dir = self.temp_dir / 'outframes'
30+
self.frames_dir = self.temp_dir / 'frames'
3231

3332
self.framecount = int(framecount)
3433
self.frame_length = frame_length
3534

3635
self.color_mode = color_mode
3736

3837
self.frames_written = 0
38+
self.out_proc = self.create_output_proc()
3939

4040
def get_frame(self,idx):
41-
assert(idx < self.framecount)
42-
41+
try:
42+
assert(idx < self.framecount)
43+
except AssertionError:
44+
print("ERROR:")
45+
print(f"STAMMER just tried to use carrier frame {idx}")
46+
print(f"but carrier only has {self.framecount} frames.")
47+
print()
48+
print("This is a critical known issue with how carrier frames are handled.")
49+
print("Please report STAMMER's output at this link:\nhttps://github.com/ArdenButterfield/stammer/issues/62")
50+
print("\nQuitting.")
51+
quit()
52+
4353
def write_frame(self):
4454
self.frames_written += 1
4555
self.print_progress()
@@ -61,54 +71,65 @@ def progress_strings_separated(self):
6171
def print_progress(self):
6272
print(self.progress_strings_separated(),end=' \r')
6373

64-
def get_output_cmd(handler: VideoHandler,input):
65-
cmd = [
66-
'ffmpeg',
67-
'-v', 'quiet',
68-
'-y',
69-
'-framerate', str(1.0/handler.frame_length),
70-
'!inputs!',
71-
'-c:a', 'aac',
72-
'-c:v', 'libx264',
73-
'-crf', '20',
74-
'-pix_fmt', 'yuv420p',
75-
'-shortest',
76-
str(handler.output_path)
77-
]
78-
79-
def replace(value, list):
80-
idx = cmd.index(value)
81-
cmd.pop(idx)
82-
for i, x in enumerate(list): cmd.insert(idx+i,x)
83-
84-
replace('!inputs!',input)
74+
def get_output_cmd(self,input = None):
75+
if input == None:
76+
input = [
77+
'-f', 'image2pipe', '-i', 'pipe:',
78+
'-i', str(self.temp_dir / 'out.wav')
79+
]
80+
cmd = [
81+
'ffmpeg',
82+
'-v', 'quiet',
83+
'-y',
84+
'-framerate', str(1.0/self.frame_length),
85+
'!inputs!',
86+
'-c:a', 'aac',
87+
'-c:v', 'libx264',
88+
'-crf', '24',
89+
'-pix_fmt', 'yuv420p',
90+
'-shortest',
91+
str(self.output_path)
92+
]
93+
94+
def replace(value, list):
95+
idx = cmd.index(value)
96+
cmd.pop(idx)
97+
for i, x in enumerate(list): cmd.insert(idx+i,x)
98+
99+
replace('!inputs!',input)
100+
101+
return cmd
85102

86-
return cmd
103+
def create_output_proc(self):
104+
call = self.get_output_cmd()
105+
106+
return subprocess.Popen(
107+
call,
108+
stdin=subprocess.PIPE,
109+
stdout=subprocess.DEVNULL
110+
)
111+
87112

88113
class VideoHandlerDisk(VideoHandler):
114+
def __init__(self, *args):
115+
super().__init__(*args)
116+
89117
def get_frame(self,idx):
90118
super().get_frame(idx)
91-
f = open(self.frames_dir / f"frame{idx:06d}.png", 'rb')
92-
return f
119+
120+
# Video frame filenames start at 1, not 0
121+
idx += 1
122+
return open(self.frames_dir / f"frame{idx:06d}.png", 'rb')
93123

94124
def write_frame(self,idx,frame: io.BytesIO):
95125
super().write_frame()
96126
frame.seek(0)
97-
f = open(self.outframes_dir / f"frame{idx:06d}.png", 'wb')
98-
f.write(frame.read())
99-
f.close()
127+
self.out_proc.stdin.write(frame.read())
100128

101129
def complete(self):
102130
super().complete()
103-
call = get_output_cmd(
104-
self,
105-
input=[
106-
'-stats',
107-
'-i', str(self.outframes_dir / 'frame%06d.png'),
108-
'-i', str(self.temp_dir / 'out.wav')
109-
]
110-
)
111-
subprocess.run(call,check=True)
131+
132+
self.out_proc.communicate()
112133

113134

114135
PNG_MAGIC = int("89504e47",16).to_bytes(4,byteorder='big')
@@ -123,8 +144,6 @@ def __init__(self, *args):
123144
self.frame_length_max = self.frame_length / max(self.frame_length,self.matcher.frame_length)
124145
self.frames_backtrack = 0
125146
self.frames_lookahead = int(max(1.0/self.frame_length_max,2))
126-
127-
self.out_proc = self.__create_output_proc()
128147

129148
def set_min_cached_frames(self,mcf):
130149
# if a decayed frame is about to be used, we fetch the frame + this amount of frames around it
@@ -217,20 +236,6 @@ def get_progress_strings(self):
217236
strs.append(f"{self.framecount-self.cache.decayed_items}/{self.framecount} cached frames")
218237
return strs
219238

220-
def __create_output_proc(self):
221-
call = get_output_cmd(
222-
self,
223-
[
224-
'-f', 'image2pipe', '-i', 'pipe:',
225-
'-i', str(self.temp_dir / 'out.wav')
226-
]
227-
)
228-
229-
return subprocess.Popen(
230-
call,
231-
stdin=subprocess.PIPE,
232-
stdout=subprocess.DEVNULL
233-
)
234239

235240
def complete(self):
236241
super().complete()

0 commit comments

Comments
 (0)