AIGoose commited on
Commit
15ce6d7
·
1 Parent(s): d09c1d0

fix(denoise): rewrite step 9 — extract audio only then remux, avoids ffmpeg 6.1 filter_complex video mapping bug

Browse files
.github/workflows/render-testimonial.yml CHANGED
@@ -48,9 +48,9 @@ jobs:
48
  SHELL_BIN=$(npx --yes @puppeteer/browsers install chrome-headless-shell@stable 2>&1 | tail -1 | awk '{print $NF}')
49
  [ ! -x "$SHELL_BIN" ] && SHELL_BIN=$(find ~/.cache/puppeteer -name "chrome-headless-shell" -executable 2>/dev/null | head -1)
50
  echo "HYPERFRAMES_BROWSER_PATH=$SHELL_BIN" >> $GITHUB_ENV
51
- echo "Browser shell: $SHELL_BIN"
52
 
53
- - name: Download testimonial MOV from Google Drive
54
  run: |
55
  mkdir -p assets project/assets
56
  python3 - <<'PYEOF'
@@ -59,11 +59,11 @@ jobs:
59
  out = "assets/raw.MOV"
60
  gdown.download(f"https://drive.google.com/uc?id={fid}", out, quiet=False)
61
  if not os.path.exists(out):
62
- print("ERROR: download failed"); sys.exit(1)
63
  print(f"Downloaded {os.path.getsize(out)/1024/1024:.1f} MB")
64
  PYEOF
65
 
66
- - name: Convert MOV to 720p source MP4
67
  run: |
68
  ffmpeg -y -i assets/raw.MOV \
69
  -vf "scale=720:1280:force_original_aspect_ratio=increase,crop=720:1280" \
@@ -72,7 +72,7 @@ jobs:
72
  assets/source.mp4
73
  echo "Source: $(du -sh assets/source.mp4)"
74
 
75
- - name: Cut interviewer section (71-77s cluster)
76
  run: |
77
  ffmpeg -y -i assets/source.mp4 \
78
  -filter_complex "\
@@ -85,87 +85,84 @@ jobs:
85
  -c:v libx264 -preset fast -crf 22 \
86
  -c:a aac -b:a 128k -movflags +faststart \
87
  assets/cut.mp4
88
- echo "After cut: $(ffprobe -v quiet -show_entries format=duration \
89
  -of default=noprint_wrappers=1:nokey=1 assets/cut.mp4 | cut -d. -f1)s"
90
 
91
- - name: Denoise voice + jump-cut pauses
92
  run: |
 
93
  ffmpeg -y -i assets/cut.mp4 \
94
- -filter_complex "\
95
- [0:a]anlmdn=s=7:p=0.002:r=0.002:m=15[dn];\
96
- [dn]silenceremove=start_periods=1:start_silence=0.3:start_threshold=-35dB\
97
- :stop_periods=-1:stop_silence=0.05:stop_threshold=-35dB[clean]" \
98
- -map "[0:v]" -map "[clean]" \
99
- -c:v copy -c:a aac -b:a 128k \
100
- assets/jumpcut.mp4
 
 
 
 
 
 
 
 
 
 
101
  DUR=$(ffprobe -v quiet -show_entries format=duration \
102
- -of default=noprint_wrappers=1:nokey=1 assets/jumpcut.mp4 | xargs printf "%.1f")
103
  echo "FOOTAGE_DUR=$DUR" >> $GITHUB_ENV
104
- echo "After denoise+jumpcut: ${DUR}s"
105
 
106
- - name: Transcribe + generate SRT captions
107
  env:
108
  HF_TOKEN: ${{ secrets.HF_TOKEN }}
109
  run: |
110
  mkdir -p project/assets
111
  python3 scripts/gen_captions.py \
112
- --input assets/jumpcut.mp4 \
113
  --srt-out assets/captions.srt
114
- echo "SRT preview:"; head -20 assets/captions.srt || true
115
 
116
- - name: Burn captions into video (ffmpeg, before HyperFrames)
117
  run: |
118
- # Install a system font for subtitle rendering
119
- FONT_PATH=$(fc-list | grep -i liberation | grep -i regular | head -1 | cut -d: -f1 || echo "")
120
  [ -z "$FONT_PATH" ] && FONT_PATH="/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"
121
- echo "Using font: $FONT_PATH"
122
-
123
- ffmpeg -y -i assets/jumpcut.mp4 \
124
  -vf "subtitles=assets/captions.srt:force_style='FontName=Liberation Sans,FontSize=22,PrimaryColour=&H001a2744,OutlineColour=&H00FFFFFF,Outline=10,Shadow=0,Alignment=2,MarginV=120,Bold=1'" \
125
  -c:v libx264 -preset fast -crf 22 \
126
  -c:a copy \
127
  assets/captioned.mp4
128
- echo "Captioned video: $(du -sh assets/captioned.mp4)"
129
 
130
- - name: Mix CC0 background music
131
  run: |
132
- # Use music.mp3 from repo assets/ if present, else generate tone
133
- if [ -f "assets/music.mp3" ]; then
134
- echo "Using repo music.mp3"
135
- MUSIC="assets/music.mp3"
136
- else
137
- echo "Generating lo-fi tone placeholder"
138
- ffmpeg -y -f lavfi -i \
139
- "aevalsrc=0.08*sin(2*PI*220*t)+0.05*sin(2*PI*440*t)+0.03*sin(2*PI*880*t):s=44100" \
140
- -af "volume=0.12,lowpass=f=800,highpass=f=80" \
141
- -t 120 -q:a 6 assets/music_gen.mp3 2>/dev/null
142
- MUSIC="assets/music_gen.mp3"
143
- fi
144
-
145
- DUR=$(ffprobe -v quiet -show_entries format=duration \
146
- -of default=noprint_wrappers=1:nokey=1 assets/captioned.mp4 | xargs printf "%.1f")
147
 
148
  ffmpeg -y \
149
  -i assets/captioned.mp4 \
150
- -stream_loop -1 -i "$MUSIC" \
151
- -filter_complex "\
152
- [1:a]atrim=0:${DUR},asetpts=PTS-STARTPTS,volume=0.10[music];\
153
- [0:a][music]amix=inputs=2:duration=first:dropout_transition=2[aout]" \
154
  -map "0:v" -map "[aout]" \
155
  -c:v copy -c:a aac -b:a 128k \
156
  assets/clean.mp4
157
- FOOTAGE_DUR=$(ffprobe -v quiet -show_entries format=duration \
158
- -of default=noprint_wrappers=1:nokey=1 assets/clean.mp4 | xargs printf "%.0f")
159
- TOTAL_DUR=$((FOOTAGE_DUR + 2))
160
- echo "FOOTAGE_DUR=$FOOTAGE_DUR" >> $GITHUB_ENV
161
- echo "TOTAL_DUR=$TOTAL_DUR" >> $GITHUB_ENV
162
- echo "Final clean.mp4: ${FOOTAGE_DUR}s | total with title card: ${TOTAL_DUR}s"
163
 
164
  - name: Build project folder
165
  run: |
166
  cp assets/clean.mp4 project/assets/clean.mp4
167
- [ -f "assets/music.mp3" ] && cp assets/music.mp3 project/assets/music.mp3 || \
168
- cp assets/music_gen.mp3 project/assets/music.mp3
169
  python3 scripts/patch_composition.py \
170
  --input compositions/testimonial.html \
171
  --output project/index.html \
@@ -174,12 +171,12 @@ jobs:
174
  --quote "${{ github.event.inputs.pull_quote }}" \
175
  --footage-dur "$FOOTAGE_DUR" \
176
  --total-dur "$TOTAL_DUR"
177
- echo "Project contents:"; ls -lh project/ project/assets/
178
 
179
  - name: Lint composition
180
  run: npx hyperframes lint project || true
181
 
182
- - name: Render (720x1280 @ 24fps - Bright Studio v3)
183
  env:
184
  HYPERFRAMES_BROWSER_PATH: ${{ env.HYPERFRAMES_BROWSER_PATH }}
185
  run: |
 
48
  SHELL_BIN=$(npx --yes @puppeteer/browsers install chrome-headless-shell@stable 2>&1 | tail -1 | awk '{print $NF}')
49
  [ ! -x "$SHELL_BIN" ] && SHELL_BIN=$(find ~/.cache/puppeteer -name "chrome-headless-shell" -executable 2>/dev/null | head -1)
50
  echo "HYPERFRAMES_BROWSER_PATH=$SHELL_BIN" >> $GITHUB_ENV
51
+ echo "Browser: $SHELL_BIN"
52
 
53
+ - name: Download MOV from Google Drive
54
  run: |
55
  mkdir -p assets project/assets
56
  python3 - <<'PYEOF'
 
59
  out = "assets/raw.MOV"
60
  gdown.download(f"https://drive.google.com/uc?id={fid}", out, quiet=False)
61
  if not os.path.exists(out):
62
+ sys.exit(1)
63
  print(f"Downloaded {os.path.getsize(out)/1024/1024:.1f} MB")
64
  PYEOF
65
 
66
+ - name: Convert MOV to 720p MP4
67
  run: |
68
  ffmpeg -y -i assets/raw.MOV \
69
  -vf "scale=720:1280:force_original_aspect_ratio=increase,crop=720:1280" \
 
72
  assets/source.mp4
73
  echo "Source: $(du -sh assets/source.mp4)"
74
 
75
+ - name: Trim interviewer section
76
  run: |
77
  ffmpeg -y -i assets/source.mp4 \
78
  -filter_complex "\
 
85
  -c:v libx264 -preset fast -crf 22 \
86
  -c:a aac -b:a 128k -movflags +faststart \
87
  assets/cut.mp4
88
+ echo "Cut: $(ffprobe -v quiet -show_entries format=duration \
89
  -of default=noprint_wrappers=1:nokey=1 assets/cut.mp4 | cut -d. -f1)s"
90
 
91
+ - name: Denoise audio (extract, filter, remux)
92
  run: |
93
+ # Step A: extract audio only and apply anlmdn noise reduction
94
  ffmpeg -y -i assets/cut.mp4 \
95
+ -vn -af "anlmdn=s=7:p=0.002:r=0.002:m=15" \
96
+ -c:a aac -b:a 128k \
97
+ assets/audio_clean.aac
98
+
99
+ # Step B: remux original video with clean audio
100
+ ffmpeg -y \
101
+ -i assets/cut.mp4 \
102
+ -i assets/audio_clean.aac \
103
+ -map 0:v -map 1:a \
104
+ -c:v copy -c:a copy \
105
+ -shortest \
106
+ assets/denoised.mp4
107
+
108
+ echo "Denoised: $(du -sh assets/denoised.mp4)"
109
+
110
+ - name: Set footage duration
111
+ run: |
112
  DUR=$(ffprobe -v quiet -show_entries format=duration \
113
+ -of default=noprint_wrappers=1:nokey=1 assets/denoised.mp4 | xargs printf "%.0f")
114
  echo "FOOTAGE_DUR=$DUR" >> $GITHUB_ENV
115
+ echo "Footage duration: ${DUR}s"
116
 
117
+ - name: Transcribe and generate SRT captions
118
  env:
119
  HF_TOKEN: ${{ secrets.HF_TOKEN }}
120
  run: |
121
  mkdir -p project/assets
122
  python3 scripts/gen_captions.py \
123
+ --input assets/denoised.mp4 \
124
  --srt-out assets/captions.srt
125
+ head -20 assets/captions.srt || true
126
 
127
+ - name: Burn captions into video
128
  run: |
129
+ FONT_PATH=$(fc-list | grep -i "LiberationSans-Regular" | head -1 | cut -d: -f1)
 
130
  [ -z "$FONT_PATH" ] && FONT_PATH="/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"
131
+ echo "Font: $FONT_PATH"
132
+ ffmpeg -y -i assets/denoised.mp4 \
 
133
  -vf "subtitles=assets/captions.srt:force_style='FontName=Liberation Sans,FontSize=22,PrimaryColour=&H001a2744,OutlineColour=&H00FFFFFF,Outline=10,Shadow=0,Alignment=2,MarginV=120,Bold=1'" \
134
  -c:v libx264 -preset fast -crf 22 \
135
  -c:a copy \
136
  assets/captioned.mp4
137
+ echo "Captioned: $(du -sh assets/captioned.mp4)"
138
 
139
+ - name: Mix background music
140
  run: |
141
+ DUR=$FOOTAGE_DUR
142
+ # Generate lo-fi ambient tone (CC0 — no external download needed)
143
+ ffmpeg -y -f lavfi \
144
+ -i "aevalsrc=0.06*sin(2*PI*220*t)+0.04*sin(2*PI*330*t)+0.03*sin(2*PI*110*t):s=44100" \
145
+ -af "volume=0.10,lowpass=f=600,highpass=f=60,aecho=0.6:0.3:60:0.3" \
146
+ -t "${DUR}" -q:a 6 -acodec libmp3lame \
147
+ assets/music_gen.mp3
 
 
 
 
 
 
 
 
148
 
149
  ffmpeg -y \
150
  -i assets/captioned.mp4 \
151
+ -stream_loop -1 -i assets/music_gen.mp3 \
152
+ -filter_complex \
153
+ "[1:a]atrim=0:${DUR},asetpts=PTS-STARTPTS,volume=0.10[music];\
154
+ [0:a][music]amix=inputs=2:duration=first:dropout_transition=2[aout]" \
155
  -map "0:v" -map "[aout]" \
156
  -c:v copy -c:a aac -b:a 128k \
157
  assets/clean.mp4
158
+
159
+ TOTAL=$((DUR + 3))
160
+ echo "TOTAL_DUR=$TOTAL" >> $GITHUB_ENV
161
+ echo "clean.mp4: $(du -sh assets/clean.mp4) | footage=${DUR}s total=${TOTAL}s"
 
 
162
 
163
  - name: Build project folder
164
  run: |
165
  cp assets/clean.mp4 project/assets/clean.mp4
 
 
166
  python3 scripts/patch_composition.py \
167
  --input compositions/testimonial.html \
168
  --output project/index.html \
 
171
  --quote "${{ github.event.inputs.pull_quote }}" \
172
  --footage-dur "$FOOTAGE_DUR" \
173
  --total-dur "$TOTAL_DUR"
174
+ echo "Project:"; ls -lh project/ project/assets/
175
 
176
  - name: Lint composition
177
  run: npx hyperframes lint project || true
178
 
179
+ - name: Render (720x1280 @ 24fps)
180
  env:
181
  HYPERFRAMES_BROWSER_PATH: ${{ env.HYPERFRAMES_BROWSER_PATH }}
182
  run: |