mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-11 17:26:45 +02:00
feat: optimize facebook reels video quality (1080p, 30fps, 10Mbps) and fix camera capture settings
This commit is contained in:
1 parent
c8943accb8
commit
456139cf2d
3 files changed
+75
-19
No files matched your search
@@ -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
@@ -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
|
||||
|
||||
@@ -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éé
|
||||
|
||||
Reference in new issue
Block a user