feat: optimize facebook reels video quality (1080p, 30fps, 10Mbps) and fix camera capture settings

This commit is contained in:
Michael committed 2026-01-29 11:19:40 +01:00
1 parent c8943accb8
commit 456139cf2d
3 files changed
+75 -19

No files matched your search

+3 -3
View File
@@ -21,9 +21,9 @@ interface QualityConfig {
}
const QUALITY_CONFIGS: Record<VideoQuality, QualityConfig> = {
"4k": { label: "4K Ultra HD", width: 3840, height: 2160, bitrate: 50000000 },
"1080p": { label: "Full HD", width: 1920, height: 1080, bitrate: 25000000 },
"720p": { label: "HD 720p", width: 1280, height: 720, bitrate: 12000000 },
"4k": { label: "4K Vertical", width: 2160, height: 3840, bitrate: 40000000 },
"1080p": { label: "Full HD (Reel)", width: 1080, height: 1920, bitrate: 12000000 }, // Align with 12Mbps max spec
"720p": { label: "HD Vertical", width: 720, height: 1280, bitrate: 8000000 },
};
export function CameraRecorder({ onCapture, onCancel }: CameraRecorderProps) {
+63 -14
View File
@@ -24,11 +24,14 @@ TEMP_DIR.mkdir(parents=True, exist_ok=True)
os.environ["HOME"] = "/tmp"
os.environ["XDG_CACHE_HOME"] = "/tmp/.cache"
# List available filters and fonts for debugging
def run_diagnostics():
print("📋 Checking FFmpeg environment...")
try:
filters_out = subprocess.run(["ffmpeg", "-filters"], capture_output=True, text=True).stdout
filters_out = subprocess.run(
["ffmpeg", "-filters"], capture_output=True, text=True
).stdout
has_subtitles = "subtitles" in filters_out
has_drawtext = "drawtext" in filters_out
print(f"✅ Filters found: subtitles={has_subtitles}, drawtext={has_drawtext}")
@@ -38,25 +41,30 @@ def run_diagnostics():
except Exception as e:
print(f"⚠️ Failed to check FFmpeg environment: {e}")
run_diagnostics()
@app.get("/debug-ffmpeg")
async def debug_ffmpeg():
try:
filters = subprocess.run(["ffmpeg", "-filters"], capture_output=True, text=True).stdout
filters = subprocess.run(
["ffmpeg", "-filters"], capture_output=True, text=True
).stdout
fonts = subprocess.run(["fc-list"], capture_output=True, text=True).stdout
return {
"filters_summary": {
"subtitles": "subtitles" in filters,
"drawtext": "drawtext" in filters
"drawtext": "drawtext" in filters,
},
"env": {k: v for k, v in os.environ.items() if "API" not in k},
"fonts": fonts.splitlines()[:50], # First 50
"raw_filters_hint": filters[:500]
"raw_filters_hint": filters[:500],
}
except Exception as e:
return {"error": str(e)}
# Robust font detection
def get_font_path():
possible_paths = [
@@ -85,6 +93,7 @@ def get_font_path():
return "Sans" # Generic fallback
FONT_PATH = get_font_path()
@@ -217,14 +226,18 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
# Escape special characters if any
sanitized_chunk = chunk.replace("{", "(").replace("}", ")")
events += f"Dialogue: 0,{start_time},{end_time},Default,,0,0,0,,{sanitized_chunk}\n"
events += (
f"Dialogue: 0,{start_time},{end_time},Default,,0,0,0,,{sanitized_chunk}\n"
)
current_time += duration
with open(ass_path, "w", encoding="utf-8") as f:
f.write(header + events)
print(f"📄 Generated ASS file: {ass_path.stat().st_size} bytes with {len(chunks)} chunks")
print(
f"📄 Generated ASS file: {ass_path.stat().st_size} bytes with {len(chunks)} chunks"
)
def format_ass_time(seconds: float) -> str:
@@ -237,13 +250,16 @@ def format_ass_time(seconds: float) -> str:
def generate_simple_srt(text: str, srt_path: Path):
"""Format seconds as SRT timestamp (HH:MM:SS,mmm)."""
# This was a stub, but let's fix the internal helper if it were called
def format_srt_time(seconds: float) -> str:
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
millis = int((seconds % 1) * 1000)
return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
pass
def format_vtt_time(seconds: float) -> str:
"""Format seconds as VTT timestamp (HH:MM:SS.mmm)."""
@@ -413,10 +429,20 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)):
f"vidstabtransform=input={transforms_path}:smoothing=10:crop=black:zoom=1,unsharp=5:5:0.8:3:3:0.4",
"-c:v",
"libx264",
"-profile:v",
"high",
"-r",
"30",
"-preset",
"medium",
"-crf",
"18",
"17",
"-b:v",
"10M",
"-maxrate",
"12M",
"-bufsize",
"20M",
"-c:a",
"copy",
str(stabilized_path),
@@ -455,8 +481,15 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)):
has_original_audio = False
try:
probe_cmd = [
"ffprobe", "-v", "error", "-select_streams", "a:0",
"-show_entries", "stream=codec_type", "-of", "csv=p=0",
"ffprobe",
"-v",
"error",
"-select_streams",
"a:0",
"-show_entries",
"stream=codec_type",
"-of",
"csv=p=0",
str(input_video_path),
]
probe_out = subprocess.check_output(probe_cmd).decode().strip()
@@ -503,7 +536,9 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)):
text_filter = f"subtitles='{ass_path_str}'"
else:
# Standard Text (without TTS)
print(f"🎬 Overlaying subtitles from standard text: {request.text[:30]}...")
print(
f"🎬 Overlaying subtitles from standard text: {request.text[:30]}..."
)
std_ass_path = job_dir / "std_text.ass"
# Use fontsize 40 by default for standard text
generate_simple_ass(request.text, std_ass_path, font_size=40)
@@ -535,7 +570,9 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)):
if has_music:
# Adjust volume
fc_parts.append(f"[{music_idx}:a]volume={request.music_volume}[a_music]")
fc_parts.append(
f"[{music_idx}:a]volume={request.music_volume}[a_music]"
)
audio_mix_str += "[a_music]"
inputs_for_mix += 1
@@ -547,7 +584,9 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)):
# Mix
if inputs_for_mix > 0:
fc_parts.append(f"{audio_mix_str}amix=inputs={inputs_for_mix}:duration=first:dropout_transition=2:normalize=0[aout]")
fc_parts.append(
f"{audio_mix_str}amix=inputs={inputs_for_mix}:duration=first:dropout_transition=2:normalize=0[aout]"
)
audio_mapped = True
else:
# No external audio added
@@ -577,10 +616,20 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)):
[
"-c:v",
"libx264",
"-profile:v",
"high",
"-r",
"30",
"-preset",
"slow",
"-crf",
"17",
"-b:v",
"10M",
"-maxrate",
"12M",
"-bufsize",
"20M",
"-c:a",
"aac",
"-b:a",
@@ -603,7 +652,7 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)):
print(f"❌ FFmpeg failed. Stderr:\n{process.stderr.decode()}")
else:
# Check stderr for font warnings even on success
stderr_last_lines = '\n'.join(process.stderr.decode().splitlines()[-20:])
stderr_last_lines = "\n".join(process.stderr.decode().splitlines()[-20:])
print(f"✅ FFmpeg executed. Stderr (last 20 lines):\n{stderr_last_lines}")
stats["encoding_duration"] = time.time() - start_step
+9 -2
View File
@@ -102,13 +102,14 @@ Ajout d'une fonctionnalité complète de création de Reels Facebook permettant
- [x] **Fix API 500** (`server/routes/reels.ts`) : Variable `stabilize` manquante corrigée.
- [x] **Fix iOS Upload** (`client/src/pages/new-reel.tsx`) : Ajout support `.mov` et augmentation limite taille (500MB).
- [ ] **Support Gros Fichiers (4GB)** : Passage à DiskStorage et streaming Cloudinary.
- [ ] **Phase 10: Optimisation 1080p & Qualité Maximale** ⏳
- [x] **Phase 10: Optimisation 1080p & Qualité Maximale** ✅
- [x] FFmpeg: Forcer scaling 1080x1920 (Vertical HD)
- [x] FFmpeg: Augmenter CRF à 17 et preset à `slow`
- [x] FFmpeg: Augmenter taille police par défaut à 64
- [x] FFmpeg: Positionner texte plus bas (proportionnel 1080p)
- [x] FFmpeg: Profil H.264 High, 30fps et bitrate 10Mbps
- [x] Frontend: Ajouter toggle Stabilisation et Qualité
- [ ] **Phase 11: Clarification UX Mobile** ✅
- [x] **Phase 11: Clarification UX Mobile** ✅
- [x] Modifier l'alerte iOS pour prévenir que les réglages se font après
- [x] Ajouter une flèche ou une animation pour attirer l'œil sur le Switch de stabilisation
- [x] S'assurer que le bouton "Suivant" est bien visible sous le Switch
@@ -125,6 +126,12 @@ Ajout d'une fonctionnalité complète de création de Reels Facebook permettant
- [x] **Final Robust Fix** : Switch complet vers le filtre `subtitles` et format `.srt` pour contourner l'absence de `drawtext` dans l'environnement.
- [x] **Style Adjustment** : Réduction de la taille du texte à 30 pour un rendu plus élégant.
### Phase 13: Activation Agent BMad ⏳
- [x] Activer l'agent `bmad-master.md`
- [x] Charger la configuration `_bmad/core/config.yaml`
- [x] Afficher le menu de l'agent en français
## Progress Log
- **22 Jan 2026** - Analyse complète et PRD créé