Para abrir un video cuando usas ./own-obs.sh 0 0 614 768 :
url="$1"
id="${url##*embed/}"
yt-dlp $1 &&
mplayer -vo fbdev2 -vf scale=1366:768,crop=608:768:379:0 -geometry 0%:0% *$id*
Graba la pantalla, audio y cámara desde Linux TTY, increíble.
#!/bin/bash
# Uso: ./shorts.sh [x y w h]
# Sin argumentos: usa región central 4:5 automática
# Ejemplo: ./shorts.sh 200 0 500 625
v4l2-ctl --device=/dev/video0 --set-fmt-video=width=1280,height=720,pixelformat=YUYV
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
AUDIO_FILE="$HOME/audio_${TIMESTAMP}.wav"
VIDEO_FILE="$HOME/recording_${TIMESTAMP}.mp4"
CAM_FILE="$HOME/cam_${TIMESTAMP}.mp4"
OUTPUT_FILE="$HOME/final_${TIMESTAMP}.mp4"
SCREEN_W=1366
SCREEN_H=768
# ── Resolución de salida (cambiá a 480 si sigue trabándose) ──
# 720p → ancho 720, alto 900 (formato 4:5)
# 480p → ancho 480, alto 600
OUT_W=720
OUT_H=900
CAM_OUT_W=720
# CAM_OUT_H se calcula en el filter_complex con -2 para que siempre sea par
# ── Región a capturar ────────────────────────────────────────
if [ $# -eq 4 ]; then
CROP_X=$1; CROP_Y=$2; CROP_W=$3; CROP_H=$4
echo "Región manual: ${CROP_W}x${CROP_H} en offset ${CROP_X},${CROP_Y}"
else
# Toda la altura (768), ancho 4:5 = 614, centrado
CROP_H=$SCREEN_H
CROP_W=$(( CROP_H * 4 / 5 & ~1 ))
CROP_X=$(( (SCREEN_W - CROP_W) / 2 ))
CROP_Y=0
echo "Región central 4:5 automática: ${CROP_W}x${CROP_H} offset ${CROP_X},${CROP_Y}"
echo "Podés especificar región: ./shorts.sh x y ancho alto"
fi
CROP_W=$(( CROP_W & ~1 ))
CROP_H=$(( CROP_H & ~1 ))
MPLAYER_PID=""; AUDIO_PID=""; CAM_PID=""
# ── Subida a YouTube ─────────────────────────────────────────
upload_youtube() {
local VIDEO_PATH="$1"
echo ""
read -r -p "¿Subir a YouTube? [s/N]: " RESPUESTA
if [[ ! "$RESPUESTA" =~ ^[sS]$ ]]; then
echo "Listo. Video guardado en: $VIDEO_PATH"
return
fi
read -r -p "Título: " YT_TITLE
read -r -p "Descripción: " YT_DESC
echo "Privacidad:"
echo " 1) Público"
echo " 2) No listado"
echo " 3) Privado"
read -r -p "Elegí [1-3] (default: 2): " YT_PRIV_OPT
case "$YT_PRIV_OPT" in
1) YT_PRIVACY="public" ;;
3) YT_PRIVACY="private" ;;
*) YT_PRIVACY="unlisted" ;;
esac
echo ""
echo "Subiendo '$YT_TITLE' como [$YT_PRIVACY]..."
source "$HOME/youtube-upload/bin/activate"
"$HOME/youtube-upload/youtube-upload/bin/youtube-upload" \
--title="$YT_TITLE" \
--description="$YT_DESC" \
--recording-date="$(date +%Y-%m-%dT%H:%M:%S.0Z)" \
--privacy="$YT_PRIVACY" \
--embeddable=True \
"$VIDEO_PATH"
if [ $? -eq 0 ]; then
echo "✓ Video subido exitosamente."
else
echo "✗ Error al subir. El video sigue en: $VIDEO_PATH"
fi
}
cleanup() {
[ -n "$MPLAYER_PID" ] && kill "$MPLAYER_PID" 2>/dev/null
[ -n "$AUDIO_PID" ] && kill "$AUDIO_PID" 2>/dev/null
[ -n "$CAM_PID" ] && kill "$CAM_PID" 2>/dev/null
wait 2>/dev/null
if [ -f "$VIDEO_FILE" ] && [ -f "$CAM_FILE" ] && [ -f "$AUDIO_FILE" ]; then
echo "Combinando video, cámara y audio..."
ffmpeg \
-i "$VIDEO_FILE" \
-i "$CAM_FILE" \
-i "$AUDIO_FILE" \
-filter_complex "
[0:v]scale=${OUT_W}:${OUT_H},setsar=1[screen];
[1:v]scale=${CAM_OUT_W}:-2,setsar=1[cam];
[screen][cam]vstack=inputs=2[stacked];
[stacked]drawbox=x=0:y=0:w=iw:h=ih:color=red@0.9:t=18[framed];
[framed]drawbox=x=8:y=8:w=iw-16:h=ih-16:color=red@0.5:t=4,
drawtext=text='● REC':fontcolor=red:fontsize=40:x=20:y=20:
fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf[out]
" \
-map "[out]" -map "2:a" \
-c:v libx264 -preset ultrafast -crf 30 \
-c:a aac -b:a 96k -shortest \
"$OUTPUT_FILE" -y
rm -f "$AUDIO_FILE" "$VIDEO_FILE" "$CAM_FILE"
echo "Guardado en: $OUTPUT_FILE"
upload_youtube "$OUTPUT_FILE"
fi
exit 0
}
trap 'cleanup' EXIT INT TERM
# ── Preview cámara ───────────────────────────────────────────
mplayer -vo fbdev2 \
-tv driver=v4l2:device=/dev/video0:width=640:height=480 tv:// \
-zoom -geometry 90%:90% &
MPLAYER_PID=$!
# ── Grabar cámara (640x480 → mejor calidad que 320x240) ──────
ffmpeg -f v4l2 -video_size 640x480 -framerate 30 -i /dev/video0 \
-c:v libx264 -preset ultrafast -crf 28 \
"$CAM_FILE" -y &
CAM_PID=$!
# ── Grabar audio ─────────────────────────────────────────────
MONITOR=$(pactl list sources short | grep monitor | head -1 | awk '{print $2}')
ffmpeg -f pulse -i "$MONITOR" -f pulse -i default \
-filter_complex "[0:a][1:a]amix=inputs=2[aout]" \
-map "[aout]" "$AUDIO_FILE" -y &
AUDIO_PID=$!
# ── Grabar región del framebuffer sin estirar ────────────────
# kmsgrab captura pantalla completa → bajamos a CPU → crop → scale → subimos a VAAPI
sudo LIBVA_DRIVER_NAME=i965 ffmpeg \
-f kmsgrab -framerate 30 -device /dev/dri/card1 -i - \
-vaapi_device /dev/dri/renderD128 \
-vf "hwmap=derive_device=vaapi,\
scale_vaapi=w=${SCREEN_W}:h=${SCREEN_H}:format=nv12,\
hwdownload,format=nv12,\
crop=${CROP_W}:${CROP_H}:${CROP_X}:${CROP_Y},\
scale=${OUT_W}:${OUT_H},\
hwupload,scale_vaapi=format=nv12" \
-c:v h264_vaapi -global_quality 28 \
"$VIDEO_FILE" -y
#!/bin/bash
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
AUDIO_FILE="$HOME/audio_${TIMESTAMP}.wav"
VIDEO_FILE="$HOME/recording_${TIMESTAMP}.mp4"
OUTPUT_FILE="$HOME/final_${TIMESTAMP}.mp4"
MPLAYER_PID=""
ARECORD_PID=""
cleanup() {
[ -n "$MPLAYER_PID" ] && kill "$MPLAYER_PID" 2>/dev/null
[ -n "$ARECORD_PID" ] && kill "$ARECORD_PID" 2>/dev/null
wait 2>/dev/null
if [ -f "$AUDIO_FILE" ] && [ -f "$VIDEO_FILE" ]; then
ffmpeg -i "$VIDEO_FILE" -i "$AUDIO_FILE" -c:v copy -c:a aac -shortest "$OUTPUT_FILE" -y
rm -rf "$AUDIO_FILE" "$VIDEO_FILE"
fi
exit 0
}
trap 'cleanup' EXIT INT TERM
mplayer -vo fbdev2 -tv driver=v4l2:device=/dev/video0:width=120:height=140 tv:// -vf "scale=320:240" -zoom -geometry 90%:90% &
MPLAYER_PID=$!
arecord -f cd -t wav "$AUDIO_FILE" &
ARECORD_PID=$!
sudo LIBVA_DRIVER_NAME=i965 ffmpeg -f kmsgrab -framerate 30 -device /dev/dri/card1 -i - -vaapi_device /dev/dri/renderD128 -vf 'hwmap=derive_device=vaapi,scale_vaapi=w=1366:h=768:format=nv12' -c:v h264_vaapi -global_quality 25 "$VIDEO_FILE" -y
#!/bin/bash
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
AUDIO_FILE="$HOME/audio_${TIMESTAMP}.wav"
VIDEO_FILE="$HOME/recording_${TIMESTAMP}.mp4"
CAM_FILE="$HOME/cam_${TIMESTAMP}.mp4"
OUTPUT_FILE="$HOME/final_${TIMESTAMP}.mp4"
AUDIO_PID=""
CAM_PID=""
# Tamaño del pip (cámara superpuesta)
PIP_W=320
PIP_H=240
# Resolución de pantalla grabada
SCR_W=1366
SCR_H=768
# Posición esquina inferior derecha con margen de 20px
PIP_X=$((SCR_W - PIP_W - 20))
PIP_Y=$((SCR_H - PIP_H - 20))
cleanup() {
[ -n "$AUDIO_PID" ] && kill "$AUDIO_PID" 2>/dev/null
[ -n "$CAM_PID" ] && kill "$CAM_PID" 2>/dev/null
wait 2>/dev/null
if [ -f "$VIDEO_FILE" ] && [ -f "$CAM_FILE" ] && [ -f "$AUDIO_FILE" ]; then
echo "Combinando video, cámara y audio..."
ffmpeg \
-i "$VIDEO_FILE" \
-i "$CAM_FILE" \
-i "$AUDIO_FILE" \
-filter_complex "
[0:v]scale=${SCR_W}:${SCR_H},setsar=1[screen];
[1:v]scale=${PIP_W}:${PIP_H},setsar=1[cam];
[screen][cam]overlay=x=${PIP_X}:y=${PIP_Y}[pip];
[pip]drawtext=text='● REC':fontcolor=red:fontsize=36:x=20:y=20:
fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf[out]
" \
-map "[out]" \
-map "2:a" \
-c:v libx264 -preset fast -crf 23 \
-c:a aac \
-shortest \
"$OUTPUT_FILE" -y
rm -f "$AUDIO_FILE" "$VIDEO_FILE" "$CAM_FILE"
echo "Guardado en: $OUTPUT_FILE"
fi
exit 0
}
trap 'cleanup' EXIT INT TERM
# Grabar cámara
ffmpeg -f v4l2 -video_size 320x240 -framerate 30 -i /dev/video0 \
-c:v libx264 -preset ultrafast -crf 28 \
"$CAM_FILE" -y &
CAM_PID=$!
# Grabar audio interno + micrófono mezclados
MONITOR=$(pactl list sources short | grep monitor | head -1 | awk '{print $2}')
ffmpeg -f pulse -i "$MONITOR" -f pulse -i default \
-filter_complex "[0:a][1:a]amix=inputs=2[aout]" \
-map "[aout]" \
"$AUDIO_FILE" -y &
AUDIO_PID=$!
# Grabar pantalla
sudo LIBVA_DRIVER_NAME=i965 ffmpeg \
-f kmsgrab -framerate 30 -device /dev/dri/card1 -i - \
-vaapi_device /dev/dri/renderD128 \
-vf 'hwmap=derive_device=vaapi,scale_vaapi=w=1366:h=768:format=nv12' \
-c:v h264_vaapi -global_quality 25 \
"$VIDEO_FILE" -y
Empezá a escribir desde la 4ta linea
bash shortmaker.sh 0 0 614 768
#!/bin/bash
# Uso: ./shorts.sh [x y w h]
# Sin argumentos: usa región central 4:5 automática
# Ejemplo: ./shorts.sh 200 0 500 625
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
AUDIO_FILE="$HOME/audio_${TIMESTAMP}.wav"
VIDEO_FILE="$HOME/recording_${TIMESTAMP}.mp4"
CAM_FILE="$HOME/cam_${TIMESTAMP}.mp4"
OUTPUT_FILE="$HOME/final_${TIMESTAMP}.mp4"
SCREEN_W=1366
SCREEN_H=768
# ── Región a capturar ────────────────────────────────────────
if [ $# -eq 4 ]; then
CROP_X=$1; CROP_Y=$2; CROP_W=$3; CROP_H=$4
echo "Región manual: ${CROP_W}x${CROP_H} en offset ${CROP_X},${CROP_Y}"
else
# Toda la altura (768), ancho 4:5 = 614, centrado
CROP_H=$SCREEN_H
CROP_W=$(( CROP_H * 4 / 5 & ~1 ))
CROP_X=$(( (SCREEN_W - CROP_W) / 2 ))
CROP_Y=0
echo "Región central 4:5 automática: ${CROP_W}x${CROP_H} offset ${CROP_X},${CROP_Y}"
echo "Podés especificar región: ./shorts.sh x y ancho alto"
fi
CROP_W=$(( CROP_W & ~1 ))
CROP_H=$(( CROP_H & ~1 ))
MPLAYER_PID=""; AUDIO_PID=""; CAM_PID=""
# ── Subida a YouTube ─────────────────────────────────────────
upload_youtube() {
local VIDEO_PATH="$1"
echo ""
read -r -p "¿Subir a YouTube? [s/N]: " RESPUESTA
if [[ ! "$RESPUESTA" =~ ^[sS]$ ]]; then
echo "Listo. Video guardado en: $VIDEO_PATH"
return
fi
read -r -p "Título: " YT_TITLE
read -r -p "Descripción: " YT_DESC
echo "Privacidad:"
echo " 1) Público"
echo " 2) No listado"
echo " 3) Privado"
read -r -p "Elegí [1-3] (default: 2): " YT_PRIV_OPT
case "$YT_PRIV_OPT" in
1) YT_PRIVACY="public" ;;
3) YT_PRIVACY="private" ;;
*) YT_PRIVACY="unlisted" ;;
esac
echo ""
echo "Subiendo '$YT_TITLE' como [$YT_PRIVACY]..."
source "$HOME/youtube-upload/bin/activate"
"$HOME/youtube-upload/youtube-upload/bin/youtube-upload" \
--title="$YT_TITLE" \
--description="$YT_DESC" \
--recording-date="$(date +%Y-%m-%dT%H:%M:%S.0Z)" \
--privacy="$YT_PRIVACY" \
--embeddable=True \
"$VIDEO_PATH"
if [ $? -eq 0 ]; then
echo "✓ Video subido exitosamente."
else
echo "✗ Error al subir. El video sigue en: $VIDEO_PATH"
fi
}
cleanup() {
[ -n "$MPLAYER_PID" ] && kill "$MPLAYER_PID" 2>/dev/null
[ -n "$AUDIO_PID" ] && kill "$AUDIO_PID" 2>/dev/null
[ -n "$CAM_PID" ] && kill "$CAM_PID" 2>/dev/null
wait 2>/dev/null
if [ -f "$VIDEO_FILE" ] && [ -f "$CAM_FILE" ] && [ -f "$AUDIO_FILE" ]; then
echo "Combinando video, cámara y audio..."
ffmpeg \
-i "$VIDEO_FILE" \
-i "$CAM_FILE" \
-i "$AUDIO_FILE" \
-filter_complex "
[0:v]scale=1080:1350,setsar=1[screen];
[1:v]scale=1080:570,setsar=1[cam];
[screen][cam]vstack=inputs=2[stacked];
[stacked]drawbox=x=0:y=0:w=iw:h=ih:color=red@0.9:t=18[framed];
[framed]drawbox=x=8:y=8:w=iw-16:h=ih-16:color=red@0.5:t=4,
drawtext=text='● REC':fontcolor=red:fontsize=60:x=30:y=30:
fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf[out]
" \
-map "[out]" -map "2:a" \
-c:v libx264 -preset ultrafast -crf 28 \
-c:a aac -b:a 128k -shortest \
"$OUTPUT_FILE" -y
rm -f "$AUDIO_FILE" "$VIDEO_FILE" "$CAM_FILE"
echo "Guardado en: $OUTPUT_FILE"
upload_youtube "$OUTPUT_FILE"
fi
exit 0
}
trap 'cleanup' EXIT INT TERM
# ── Preview cámara ───────────────────────────────────────────
mplayer -vo fbdev2 \
-tv driver=v4l2:device=/dev/video0:width=320:height=240 tv:// \
-zoom -geometry 90%:90% &
MPLAYER_PID=$!
# ── Grabar cámara ────────────────────────────────────────────
ffmpeg -f v4l2 -video_size 320x240 -framerate 30 -i /dev/video0 \
-c:v libx264 -preset ultrafast -crf 28 \
"$CAM_FILE" -y &
CAM_PID=$!
# ── Grabar audio ─────────────────────────────────────────────
MONITOR=$(pactl list sources short | grep monitor | head -1 | awk '{print $2}')
ffmpeg -f pulse -i "$MONITOR" -f pulse -i default \
-filter_complex "[0:a][1:a]amix=inputs=2[aout]" \
-map "[aout]" "$AUDIO_FILE" -y &
AUDIO_PID=$!
# ── Grabar región del framebuffer sin estirar ────────────────
# kmsgrab captura pantalla completa → bajamos a CPU → crop → scale → subimos a VAAPI
sudo LIBVA_DRIVER_NAME=i965 ffmpeg \
-f kmsgrab -framerate 30 -device /dev/dri/card1 -i - \
-vaapi_device /dev/dri/renderD128 \
-vf "hwmap=derive_device=vaapi,\
scale_vaapi=w=${SCREEN_W}:h=${SCREEN_H}:format=nv12,\
hwdownload,format=nv12,\
crop=${CROP_W}:${CROP_H}:${CROP_X}:${CROP_Y},\
scale=1080:1350,\
hwupload,scale_vaapi=format=nv12" \
-c:v h264_vaapi -global_quality 25 \
"$VIDEO_FILE" -y
" Vim Configuration based on blog posts
" ============================================ " BASIC SETTINGS " ============================================
set number
" Marca visual del límite de la región grabada " Fuente latarcyrheb-sun32 = 16px por carácter, región 614px → 38 cols, -1 offset set colorcolumn=34 highlight ColorColumn ctermbg=red ctermfg=white
" Resaltar texto que se pase del límite highlight OverLength ctermbg=darkred ctermfg=white match OverLength /%38v.+/
set nocompatible syntax on “set number “set relativenumber set background=dark set hlsearch set incsearch set ignorecase set noswapfile set ai set ic
" Tab settings set tabstop=4 set shiftwidth=4 set expandtab
" Mouse support set mouse=
" Color scheme colorscheme default
" ============================================ " COPY & PASTE " ============================================ " Copy to clipboard in Wayland xnoremap <C-@> :w !wl-copy
" ============================================ " SPELL CHECK " ============================================ " Download spell files: " mkdir -p ~/.vim/spell " cd ~/.vim/spell " wget –no-check-certificate https://ftp.nluug.nl/vim/runtime/spell/es.latin1.spl " wget –no-check-certificate https://ftp.nluug.nl/vim/runtime/spell/es.latin1.sug " wget –no-check-certificate https://ftp.nluug.nl/vim/runtime/spell/es.utf-8.spl " wget –no-check-certificate https://ftp.nluug.nl/vim/runtime/spell/es.utf-8.sug setlocal spell spelllang=en,es
" ============================================ " HEXADECIMAL EDITING " ============================================ augroup Binary au! au BufReadPre *.bin let &bin=1 au BufReadPost *.bin if &bin | %!xxd | endif au BufReadPost *.bin set ft=xxd au BufWritePre *.bin if &bin | %!xxd -r | endif au BufWritePost *.bin if &bin | %!xxd | endif au BufWritePost *.bin set nomod augroup END
" ============================================ " TMUX/SCREEN KEYBINDINGS " ============================================ if &term =~ ‘^screen’ execute “set =\e[1;*A” execute “set =\e[1;*B” execute “set =\e[1;*C” execute “set =\e[1;*D” endif
" ============================================
" VIM-PLUG SETUP (optional)
" ============================================
" Install: curl -fLo ~/.vim/autoload/plug.vim –create-dirs
" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
" Then run :PlugInstall
" call plug#begin(’~/.vim/plugged’) " Plug ‘junegunn/fzf’, { ‘do’: { -> fzf#install() } } " Plug ‘junegunn/fzf.vim’ " Plug ‘gruvbox-community/gruvbox’ " Plug ’nanotech/jellybeans.vim’ " call plug#end()
#!/bin/bash
# Uso: ./shorts.sh [x y w h]
# Sin argumentos: usa región central 4:5 automática
# Ejemplo: ./shorts.sh 200 0 500 625
# ── Configuración de cámara ──────────────────────────────────
# Resolución y formato (640x480 en lugar de 320x240)
v4l2-ctl --device=/dev/video0 --set-fmt-video=width=640,height=480,pixelformat=YUYV
# Ajustes de imagen (descomenta y ajustá según tu cámara)
# Listá los controles disponibles con: v4l2-ctl -d /dev/video0 --list-ctrls
v4l2-ctl --device=/dev/video0 \
--set-ctrl=brightness=10 \
--set-ctrl=contrast=128 \
--set-ctrl=saturation=150 \
--set-ctrl=sharpness=128 \
--set-ctrl=white_balance_automatic=1 \
--set-ctrl=auto_exposure=3 2>/dev/null || true
# auto_exposure: 1=manual, 3=auto (varía por cámara)
# Si tu cámara soporta backlight_compensation:
# --set-ctrl=backlight_compensation=1
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
AUDIO_FILE="$HOME/audio_${TIMESTAMP}.wav"
VIDEO_FILE="$HOME/recording_${TIMESTAMP}.mp4"
CAM_FILE="$HOME/cam_${TIMESTAMP}.mp4"
OUTPUT_FILE="$HOME/final_${TIMESTAMP}.mp4"
SCREEN_W=1366
SCREEN_H=768
# ── Región a capturar ────────────────────────────────────────
if [ $# -eq 4 ]; then
CROP_X=$1; CROP_Y=$2; CROP_W=$3; CROP_H=$4
echo "Región manual: ${CROP_W}x${CROP_H} en offset ${CROP_X},${CROP_Y}"
else
CROP_H=$SCREEN_H
CROP_W=$(( CROP_H * 4 / 5 & ~1 ))
CROP_X=$(( (SCREEN_W - CROP_W) / 2 ))
CROP_Y=0
echo "Región central 4:5 automática: ${CROP_W}x${CROP_H} offset ${CROP_X},${CROP_Y}"
echo "Podés especificar región: ./shorts.sh x y ancho alto"
fi
CROP_W=$(( CROP_W & ~1 ))
CROP_H=$(( CROP_H & ~1 ))
MPLAYER_PID=""; AUDIO_PID=""; CAM_PID=""
# ── Subida a YouTube ─────────────────────────────────────────
upload_youtube() {
local VIDEO_PATH="$1"
echo ""
read -r -p "¿Subir a YouTube? [s/N]: " RESPUESTA
if [[ ! "$RESPUESTA" =~ ^[sS]$ ]]; then
echo "Listo. Video guardado en: $VIDEO_PATH"
return
fi
read -r -p "Título: " YT_TITLE
read -r -p "Descripción: " YT_DESC
echo "Privacidad:"
echo " 1) Público"
echo " 2) No listado"
echo " 3) Privado"
read -r -p "Elegí [1-3] (default: 2): " YT_PRIV_OPT
case "$YT_PRIV_OPT" in
1) YT_PRIVACY="public" ;;
3) YT_PRIVACY="private" ;;
*) YT_PRIVACY="unlisted" ;;
esac
echo ""
echo "Subiendo '$YT_TITLE' como [$YT_PRIVACY]..."
source "$HOME/youtube-upload/bin/activate"
"$HOME/youtube-upload/youtube-upload/bin/youtube-upload" \
--title="$YT_TITLE" \
--description="$YT_DESC" \
--recording-date="$(date +%Y-%m-%dT%H:%M:%S.0Z)" \
--privacy="$YT_PRIVACY" \
--embeddable=True \
"$VIDEO_PATH"
if [ $? -eq 0 ]; then
echo "✓ Video subido exitosamente."
else
echo "✗ Error al subir. El video sigue en: $VIDEO_PATH"
fi
}
cleanup() {
[ -n "$MPLAYER_PID" ] && kill "$MPLAYER_PID" 2>/dev/null
[ -n "$AUDIO_PID" ] && kill "$AUDIO_PID" 2>/dev/null
[ -n "$CAM_PID" ] && kill "$CAM_PID" 2>/dev/null
wait 2>/dev/null
if [ -f "$VIDEO_FILE" ] && [ -f "$CAM_FILE" ] && [ -f "$AUDIO_FILE" ]; then
echo "Combinando video, cámara y audio..."
ffmpeg \
-i "$VIDEO_FILE" \
-i "$CAM_FILE" \
-i "$AUDIO_FILE" \
-filter_complex "
[0:v]scale=1080:1350,setsar=1[screen];
[1:v]scale=1080:570,
unsharp=3:3:0.8:3:3:0,
eq=brightness=0.02:contrast=1.05:saturation=1.1,
setsar=1[cam];
[screen][cam]vstack=inputs=2[stacked];
[stacked]drawbox=x=0:y=0:w=iw:h=ih:color=red@0.9:t=18[framed];
[framed]drawbox=x=8:y=8:w=iw-16:h=ih-16:color=red@0.5:t=4,
drawtext=text='● REC':fontcolor=red:fontsize=60:x=30:y=30:
fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf[out]
" \
-map "[out]" -map "2:a" \
-c:v libx264 -preset ultrafast -crf 28 \
-c:a aac -b:a 128k -shortest \
"$OUTPUT_FILE" -y
rm -f "$AUDIO_FILE" "$VIDEO_FILE" "$CAM_FILE"
echo "Guardado en: $OUTPUT_FILE"
upload_youtube "$OUTPUT_FILE"
fi
exit 0
}
trap 'cleanup' EXIT INT TERM
# ── Preview cámara ───────────────────────────────────────────
# Resolución de preview aumentada a 640x480
mplayer -vo fbdev2 \
-tv driver=v4l2:device=/dev/video0:width=640:height=480 tv:// \
-zoom -geometry 90%:90% &
MPLAYER_PID=$!
# ── Grabar cámara ────────────────────────────────────────────
# Resolución 640x480, sin estiramiento al grabar
# Se escala a 1080x570 solo en el merge final con filtros de mejora
ffmpeg \
-f v4l2 \
-video_size 640x480 \
-framerate 30 \
-i /dev/video0 \
-vf "unsharp=3:3:0.5:3:3:0,eq=brightness=0.02:contrast=1.03:saturation=1.08" \
-c:v libx264 -preset ultrafast -crf 23 \
"$CAM_FILE" -y &
CAM_PID=$!
# ── Grabar audio ─────────────────────────────────────────────
MONITOR=$(pactl list sources short | grep monitor | head -1 | awk '{print $2}')
ffmpeg -f pulse -i "$MONITOR" -f pulse -i default \
-filter_complex "[0:a][1:a]amix=inputs=2[aout]" \
-map "[aout]" "$AUDIO_FILE" -y &
AUDIO_PID=$!
# ── Grabar región del framebuffer sin estirar ────────────────
sudo LIBVA_DRIVER_NAME=i965 ffmpeg \
-f kmsgrab -framerate 30 -device /dev/dri/card1 -i - \
-vaapi_device /dev/dri/renderD128 \
-vf "hwmap=derive_device=vaapi,\
scale_vaapi=w=${SCREEN_W}:h=${SCREEN_H}:format=nv12,\
hwdownload,format=nv12,\
crop=${CROP_W}:${CROP_H}:${CROP_X}:${CROP_Y},\
scale=1080:1350,\
hwupload,scale_vaapi=format=nv12" \
-c:v h264_vaapi -global_quality 25 \
"$VIDEO_FILE" -y
Con mplayer y -vo fbdev2 el equivalente es:
mplayer -vo fbdev2 -vf scale=614:752 -geometry 376:16 video.mp4
-vf scale=614:752 → escala el video a ese tamaño exacto-geometry 376:16 → lo posiciona en X:Y en el framebufferSi el video es 16:9 y no querés que se estire, dejás que calcule la altura proporcional:
mplayer -vo fbdev2 -vf scale=614:-2 -geometry 376:16 video.mp4
El -2 le dice “calculá la altura manteniendo proporción y que sea par”. Para un video 16:9 quedaría en 346px de alto, centrado verticalmente en el frame grabado.
Si querés que ocupe toda la altura disponible (752px) y se recorte lo que sobre horizontalmente, usás zoom:
mplayer -vo fbdev2 -vf scale=-2:752 -zoom -geometry 376:16 video.mp4
Resumen rápido:
| Caso | Comando |
|---|---|
| Ocupa exacto el frame (puede estirar) | -vf scale=614:752 -geometry 376:16 |
| Proporcional, ancho fijo | -vf scale=614:-2 -geometry 376:16 |
| Proporcional, alto fijo | -vf scale=-2:752 -geometry 376:16 |
#!/bin/bash
# Uso: ./shorts.sh [x y w h]
# Sin argumentos: usa región central 4:5 automática
# Ejemplo: ./shorts.sh 200 0 500 625
# ── Configuración de cámara ──────────────────────────────────
v4l2-ctl --device=/dev/video0 --set-fmt-video=width=640,height=480,pixelformat=YUYV
v4l2-ctl --device=/dev/video0 \
--set-ctrl=brightness=10 \
--set-ctrl=contrast=128 \
--set-ctrl=saturation=150 \
--set-ctrl=sharpness=128 \
--set-ctrl=white_balance_automatic=1 \
--set-ctrl=auto_exposure=3 2>/dev/null || true
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
AUDIO_FILE="$HOME/audio_${TIMESTAMP}.wav"
VIDEO_FILE="$HOME/recording_${TIMESTAMP}.mp4"
CAM_FILE="$HOME/cam_${TIMESTAMP}.mp4"
OUTPUT_FILE="$HOME/final_${TIMESTAMP}.mp4"
SCREEN_W=1366
SCREEN_H=768
# ── Región a capturar ────────────────────────────────────────
if [ $# -eq 4 ]; then
CROP_X=$1; CROP_Y=$2; CROP_W=$3; CROP_H=$4
echo "Región manual: ${CROP_W}x${CROP_H} en offset ${CROP_X},${CROP_Y}"
else
CROP_H=$SCREEN_H
CROP_W=$(( CROP_H * 4 / 5 & ~1 ))
CROP_X=$(( (SCREEN_W - CROP_W) / 2 ))
CROP_Y=0
echo "Región central 4:5 automática: ${CROP_W}x${CROP_H} offset ${CROP_X},${CROP_Y}"
echo "Podés especificar región: ./shorts.sh x y ancho alto"
fi
CROP_W=$(( CROP_W & ~1 ))
CROP_H=$(( CROP_H & ~1 ))
MPLAYER_PID=""; AUDIO_PID=""; CAM_PID=""
# ── Subida a YouTube ─────────────────────────────────────────
upload_youtube() {
local VIDEO_PATH="$1"
echo ""
read -r -p "¿Subir a YouTube? [s/N]: " RESPUESTA
if [[ ! "$RESPUESTA" =~ ^[sS]$ ]]; then
echo "Listo. Video guardado en: $VIDEO_PATH"
return
fi
read -r -p "Título: " YT_TITLE
read -r -p "Descripción: " YT_DESC
echo "Privacidad:"
echo " 1) Público"
echo " 2) No listado"
echo " 3) Privado"
read -r -p "Elegí [1-3] (default: 2): " YT_PRIV_OPT
case "$YT_PRIV_OPT" in
1) YT_PRIVACY="public" ;;
3) YT_PRIVACY="private" ;;
*) YT_PRIVACY="unlisted" ;;
esac
echo ""
echo "Subiendo '$YT_TITLE' como [$YT_PRIVACY]..."
source "$HOME/youtube-upload/bin/activate"
"$HOME/youtube-upload/youtube-upload/bin/youtube-upload" \
--title="$YT_TITLE" \
--description="$YT_DESC" \
--recording-date="$(date +%Y-%m-%dT%H:%M:%S.0Z)" \
--privacy="$YT_PRIVACY" \
--embeddable=True \
"$VIDEO_PATH"
if [ $? -eq 0 ]; then
echo "✓ Video subido exitosamente."
else
echo "✗ Error al subir. El video sigue en: $VIDEO_PATH"
fi
}
cleanup() {
[ -n "$MPLAYER_PID" ] && kill "$MPLAYER_PID" 2>/dev/null
[ -n "$AUDIO_PID" ] && kill "$AUDIO_PID" 2>/dev/null
[ -n "$CAM_PID" ] && kill "$CAM_PID" 2>/dev/null
wait 2>/dev/null
if [ -f "$VIDEO_FILE" ] && [ -f "$CAM_FILE" ] && [ -f "$AUDIO_FILE" ]; then
echo "Combinando video, cámara y audio..."
ffmpeg \
-i "$VIDEO_FILE" \
-i "$CAM_FILE" \
-i "$AUDIO_FILE" \
-filter_complex "
[0:v]scale=1080:1350,setsar=1[screen];
[1:v]scale=1080:570,
unsharp=3:3:0.8:3:3:0,
eq=brightness=0.02:contrast=1.05:saturation=1.1,
setsar=1[cam];
[screen][cam]vstack=inputs=2[stacked];
[stacked]drawbox=x=0:y=0:w=iw:h=ih:color=red@0.9:t=18[framed];
[framed]drawbox=x=8:y=8:w=iw-16:h=ih-16:color=red@0.5:t=4,
drawtext=text='● REC':fontcolor=red:fontsize=60:x=30:y=30:
fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf[out]
" \
-map "[out]" -map "2:a" \
-c:v libx264 -preset ultrafast -crf 28 \
-c:a aac -b:a 128k -shortest \
"$OUTPUT_FILE" -y
rm -f "$AUDIO_FILE" "$VIDEO_FILE" "$CAM_FILE"
echo "Guardado en: $OUTPUT_FILE"
upload_youtube "$OUTPUT_FILE"
fi
exit 0
}
trap 'cleanup' EXIT INT TERM
# ── Preview cámara ───────────────────────────────────────────
mplayer -vo fbdev2 \
-tv driver=v4l2:device=/dev/video0:width=640:height=480 tv:// \
-zoom -geometry 90%:90% &
MPLAYER_PID=$!
# ── Grabar cámara ────────────────────────────────────────────
ffmpeg \
-f v4l2 \
-video_size 640x480 \
-framerate 30 \
-i /dev/video0 \
-vf "unsharp=3:3:0.5:3:3:0,eq=brightness=0.02:contrast=1.03:saturation=1.08" \
-c:v libx264 -preset ultrafast -crf 23 \
"$CAM_FILE" -y &
CAM_PID=$!
# ── Grabar audio ─────────────────────────────────────────────
MONITOR=$(pactl list sources short | grep monitor | head -1 | awk '{print $2}')
ffmpeg -f pulse -i "$MONITOR" -f pulse -i default \
-filter_complex "[0:a][1:a]amix=inputs=2[aout]" \
-map "[aout]" "$AUDIO_FILE" -y &
AUDIO_PID=$!
# ── Grabar pantalla (pipeline CPU puro — sin mezcla hwaccel/software) ────────
# FIX: El pipeline anterior mezclaba hwdownload con scale_vaapi al final,
# lo que causaba stalls y frames trabados al decodificar en el navegador.
# Solución: bajar a CPU con hwdownload, hacer crop+scale en software, y
# encodear con libx264 ultrafast para mantener framerate estable.
sudo LIBVA_DRIVER_NAME=i965 ffmpeg \
-f kmsgrab -framerate 30 -device /dev/dri/card1 -i - \
-vaapi_device /dev/dri/renderD128 \
-vf "hwmap=derive_device=vaapi,\
scale_vaapi=w=${SCREEN_W}:h=${SCREEN_H}:format=nv12,\
hwdownload,format=nv12,\
crop=${CROP_W}:${CROP_H}:${CROP_X}:${CROP_Y},\
scale=1080:1350" \
-c:v libx264 -preset ultrafast -crf 23 \
-g 30 -keyint_min 30 \
-x264-params "nal-hrd=cbr:force-cfr=1" \
-b:v 4M -maxrate 4M -bufsize 2M \
"$VIDEO_FILE" -y
alias mutemic=‘pactl set-source-mute @DEFAULT_SOURCE@ 1 && echo “🔇 Micro muteado”’ alias unmutemic=‘pactl set-source-mute @DEFAULT_SOURCE@ 0 && echo “🎙️ Micro activo”’ alias togglemic=‘pactl set-source-mute @DEFAULT_SOURCE@ toggle && echo “🎙️ Micro toggled”’
mplayer -vo fbdev2 -vf scale=-2:340 -geometry 0:0 *mp4
# ~/.tmux.conf
set -g default-terminal "screen-256color"
setw -g aggressive-resize off
set -g status-style "bg=black,fg=green"
set -g status-left ""
set -g status-right " arch install "
set -g status-justify centre
# forzar tamaño en cada cliente que se conecta
set-hook -g client-attached 'resize-window -x 38 -y 24'
set-hook -g client-session-changed 'resize-window -x 38 -y 24'
tmux new-session -s record -x 38 -y 24
Entonces el video tiene que ser 608×768px exacto.
Si el video original es 16:9 (típico), escalado a 608 de ancho:
608 × 9/16 = 342px alto
Para llenarlo verticalmente escalás al alto y croppeás los costados:
768px alto → ancho proporcional 768 × 16/9 = 1365px
crop al centro: (1365 - 608) / 2 = 378px de cada lado
mplayer -vo fbdev2 \
-vf scale=1366:768,crop=608:768:379:0 \
*.mp4
Si el video es vertical (9:16):
mplayer -vo fbdev2 \
-vf scale=608:768 \
*.mp4
Si el video es cuadrado:
mplayer -vo fbdev2 \
-vf scale=608:608,expand=608:768:0:80 \
*.mp4
mplayer -identify -frames 0 *.mp4 2>/dev/null | grep VIDEO
mplayer -vo fbdev2 -vf scale=1366:768,crop=608:768:379:0 -geometry 0%:0% *mp4
#!/bin/bash
# Uso: ./own_obs.sh [x y w h]
# Sin argumentos: captura pantalla completa 1366x768 (16:9)
# Con argumentos: ./own_obs.sh 0 0 1366 768
#
# Extras disponibles (variables al inicio):
# SHOW_REC=1 → indicador ● REC
# CAM_ENABLED=1 → overlay de cámara (0 para desactivar)
# CAM_SIZE=320 → ancho del overlay de cámara en px
# CAM_SHAPE=round → "round" para cámara circular, "rect" para rectangular
# INTRO_TEXT="" → texto de intro que aparece los primeros 3 seg
# WATERMARK="" → ruta a imagen PNG para watermark (ej: ~/logo.png)
set -euo pipefail
# ════════════════════════════════════════════════════
# CONFIGURACIÓN — editá estas variables
# ════════════════════════════════════════════════════
SHOW_REC=1
CAM_ENABLED=1
CAM_SIZE=280 # ancho del recuadro de cámara en px
CAM_SHAPE=round # "round" o "rect"
INTRO_TEXT="" # ej: "Hola, bienvenidos al canal"
WATERMARK="" # ruta a logo PNG con alpha, o vacío para omitir
FONT=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf
SCREEN_W=1366
SCREEN_H=768
# Salida 16:9 — YouTube 1080p estándar
OUT_W=1280
OUT_H=720
# Cámara: forzamos 16:9 en captura para no distorsionar
CAM_INPUT_W=640
CAM_INPUT_H=360
# ════════════════════════════════════════════════════
# ARCHIVOS TEMPORALES
# ════════════════════════════════════════════════════
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
AUDIO_FILE="$HOME/audio_${TIMESTAMP}.wav"
VIDEO_FILE="$HOME/recording_${TIMESTAMP}.mp4"
CAM_FILE="$HOME/cam_${TIMESTAMP}.mp4"
OUTPUT_FILE="$HOME/final_${TIMESTAMP}.mp4"
# ════════════════════════════════════════════════════
# REGIÓN A CAPTURAR
# ════════════════════════════════════════════════════
if [ $# -eq 4 ]; then
CROP_X=$1; CROP_Y=$2; CROP_W=$3; CROP_H=$4
echo "Región manual: ${CROP_W}x${CROP_H} en offset ${CROP_X},${CROP_Y}"
else
CROP_X=0; CROP_Y=0
CROP_W=$SCREEN_W; CROP_H=$SCREEN_H
echo "Captura completa: ${CROP_W}x${CROP_H}"
fi
CROP_W=$(( CROP_W & ~1 ))
CROP_H=$(( CROP_H & ~1 ))
# ════════════════════════════════════════════════════
# PIDS
# ════════════════════════════════════════════════════
MPLAYER_PID=""; AUDIO_PID=""; CAM_PID=""
# ════════════════════════════════════════════════════
# SUBIDA A YOUTUBE
# ════════════════════════════════════════════════════
upload_youtube() {
local VIDEO_PATH="$1"
echo ""
read -r -p "¿Subir a YouTube? [s/N]: " RESPUESTA
if [[ ! "$RESPUESTA" =~ ^[sS]$ ]]; then
echo "Listo. Video guardado en: $VIDEO_PATH"
return
fi
read -r -p "Título: " YT_TITLE
read -r -p "Descripción (Enter para vacía): " YT_DESC
echo "Privacidad:"
echo " 1) Público"
echo " 2) No listado"
echo " 3) Privado"
read -r -p "Elegí [1-3] (default: 2): " YT_PRIV_OPT
case "$YT_PRIV_OPT" in
1) YT_PRIVACY="public" ;;
3) YT_PRIVACY="private" ;;
*) YT_PRIVACY="unlisted" ;;
esac
echo ""
echo "Subiendo '$YT_TITLE' como [$YT_PRIVACY]..."
source "$HOME/youtube-upload/bin/activate"
"$HOME/youtube-upload/youtube-upload/bin/youtube-upload" \
--title="$YT_TITLE" \
--description="$YT_DESC" \
--recording-date="$(date +%Y-%m-%dT%H:%M:%S.0Z)" \
--privacy="$YT_PRIVACY" \
--embeddable=True \
"$VIDEO_PATH"
if [ $? -eq 0 ]; then
echo "✓ Video subido exitosamente."
else
echo "✗ Error al subir. El video sigue en: $VIDEO_PATH"
fi
}
# ════════════════════════════════════════════════════
# CLEANUP + COMPOSICIÓN FINAL
# ════════════════════════════════════════════════════
cleanup() {
[ -n "$MPLAYER_PID" ] && kill "$MPLAYER_PID" 2>/dev/null || true
[ -n "$AUDIO_PID" ] && kill "$AUDIO_PID" 2>/dev/null || true
[ -n "$CAM_PID" ] && kill "$CAM_PID" 2>/dev/null || true
wait 2>/dev/null || true
# ── Verificar que existan los archivos necesarios ────────
MISSING=0
[ ! -f "$VIDEO_FILE" ] && echo "✗ Falta video de pantalla" && MISSING=1
[ ! -f "$AUDIO_FILE" ] && echo "✗ Falta audio" && MISSING=1
if [ "$CAM_ENABLED" = "1" ] && [ ! -f "$CAM_FILE" ]; then
echo "✗ Falta video de cámara" && MISSING=1
fi
[ "$MISSING" = "1" ] && exit 1
echo ""
echo "══════════════════════════════════════"
echo " Componiendo video final..."
echo "══════════════════════════════════════"
# ── Construir filter_complex dinámicamente ───────────────
# La lógica es:
# [0:v] → pantalla escalada → [screen]
# [1:v] → cámara escalada → [cam_scaled]
# si CAM_SHAPE=round → máscara circular → [cam_masked]
# overlay de cámara en esquina inferior derecha
# luego capas de texto: REC, reloj, intro, watermark
CAM_H=$(( CAM_SIZE * CAM_INPUT_H / CAM_INPUT_W ))
CAM_H=$(( CAM_H & ~1 ))
# padding de 20px desde los bordes
CAM_PAD=20
CAM_X=$(( OUT_W - CAM_SIZE - CAM_PAD ))
CAM_Y=$(( OUT_H - CAM_H - CAM_PAD ))
# ── Inputs de ffmpeg ─────────────────────────────────────
FFMPEG_INPUTS="-i $VIDEO_FILE"
if [ "$CAM_ENABLED" = "1" ]; then
FFMPEG_INPUTS="$FFMPEG_INPUTS -i $CAM_FILE"
fi
FFMPEG_INPUTS="$FFMPEG_INPUTS -i $AUDIO_FILE"
AUDIO_IDX=2
[ "$CAM_ENABLED" != "1" ] && AUDIO_IDX=1
# ── filter_complex ───────────────────────────────────────
FC="[0:v]scale=${OUT_W}:${OUT_H},setsar=1,format=yuva420p[screen];"
if [ "$CAM_ENABLED" = "1" ]; then
FC+="[1:v]scale=${CAM_SIZE}:${CAM_H},setsar=1,format=yuva420p[cam_scaled];"
if [ "$CAM_SHAPE" = "round" ]; then
# Máscara circular usando geq: pinta alpha=0 fuera del círculo
R=$(( CAM_SIZE / 2 ))
FC+="[cam_scaled]geq=\
lum='lum(X,Y)':\
a='if(lte((X-${R})^2+(Y-${R})^2,${R}^2),255,0)'[cam_masked];"
CAM_OUT="cam_masked"
else
CAM_OUT="cam_scaled"
fi
# Borde/sombra alrededor de la cámara
if [ "$CAM_SHAPE" = "round" ]; then
# Para cámara redonda el borde se pone sobre el overlay ya compuesto
BORDER_FILTER="drawbox=x=${CAM_X}:y=${CAM_Y}:w=${CAM_SIZE}:h=${CAM_H}:color=black@0.0:t=0,"
else
BORDER_FILTER="drawbox=x=$(( CAM_X - 3 )):y=$(( CAM_Y - 3 )):w=$(( CAM_SIZE + 6 )):h=$(( CAM_H + 6 )):color=white@0.8:t=3,"
fi
FC+="[screen][${CAM_OUT}]overlay=x=${CAM_X}:y=${CAM_Y}:format=auto[with_cam];"
LAST="with_cam"
else
LAST="screen"
fi
# ── Capas de texto y decoración ──────────────────────────
TEXT_CHAIN="[${LAST}]"
# Indicador REC
if [ "$SHOW_REC" = "1" ]; then
REC_FILE=$(mktemp /tmp/rec_XXXXXX.txt)
printf '● REC' > "$REC_FILE"
TEXT_CHAIN+="drawbox=x=12:y=12:w=130:h=44:color=black@0.45:t=fill,"
TEXT_CHAIN+="drawtext=textfile=${REC_FILE}:fontcolor=red:fontsize=28:x=20:y=20:fontfile=${FONT},"
fi
# Intro text: aparece los primeros 3 segundos con fade out
if [ -n "$INTRO_TEXT" ]; then
TEXT_CHAIN+="drawtext=text=${INTRO_TEXT}:fontcolor=white:fontsize=42:"
TEXT_CHAIN+="x=(w-tw)/2:y=(h-th)/2:"
TEXT_CHAIN+="fontfile=${FONT}:"
TEXT_CHAIN+="shadowcolor=black:shadowx=3:shadowy=3:"
TEXT_CHAIN+="enable=between(t\,0\,3):"
TEXT_CHAIN+="alpha=if(lt(t\,2.5)\,1\,max(0\,1-(t-2.5)/0.5)),"
fi
# Quitar la coma final y cerrar con [out]
TEXT_CHAIN="${TEXT_CHAIN%,}[out]"
FC+="${TEXT_CHAIN}"
# Watermark (imagen PNG con alpha) — se agrega como input extra si se especifica
WM_INPUT=""
WM_EXTRA=""
if [ -n "$WATERMARK" ] && [ -f "$WATERMARK" ]; then
WM_INPUT="-i $WATERMARK"
# Reemplazar [out] final por composición con watermark
FC="${FC%\[out\]}"
WM_IDX=$(( AUDIO_IDX + 0 )) # viene después del audio
WM_INPUT="-i $WATERMARK"
FC+=";[out_pre][${WM_IDX}:v]overlay=x=w-overlay_w-20:y=h-overlay_h-20[out]"
AUDIO_IDX=$(( AUDIO_IDX + 1 ))
fi
# ── Ejecutar ffmpeg final ─────────────────────────────────
ffmpeg \
$FFMPEG_INPUTS \
$WM_INPUT \
-filter_complex "$FC" \
-map "[out]" -map "${AUDIO_IDX}:a" \
-c:v libx264 -preset fast -crf 23 \
-c:a aac -b:a 128k \
-movflags +faststart \
-shortest \
"$OUTPUT_FILE" -y
rm -f "$AUDIO_FILE" "$VIDEO_FILE" "$CAM_FILE" /tmp/rec_*.txt
echo ""
echo "══════════════════════════════════════"
echo " ✓ Guardado en: $OUTPUT_FILE"
SIZE=$(du -h "$OUTPUT_FILE" | cut -f1)
DUR=$(ffprobe -v error -show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 "$OUTPUT_FILE" 2>/dev/null \
| awk '{printf "%02d:%02d", $1/60, $1%60}')
echo " Tamaño: $SIZE Duración: ${DUR}"
echo "══════════════════════════════════════"
upload_youtube "$OUTPUT_FILE"
exit 0
}
trap 'cleanup' EXIT INT TERM
# ════════════════════════════════════════════════════
# INICIAR GRABACIONES
# ════════════════════════════════════════════════════
# ── Configurar cámara ────────────────────────────────────────
v4l2-ctl --device=/dev/video0 \
--set-fmt-video=width=${CAM_INPUT_W},height=${CAM_INPUT_H},pixelformat=YUYV
# ── Preview cámara en framebuffer ────────────────────────────
if [ "$CAM_ENABLED" = "1" ]; then
mplayer -vo fbdev2 \
-tv driver=v4l2:device=/dev/video0:width=${CAM_INPUT_W}:height=${CAM_INPUT_H} tv:// \
-zoom -geometry 95%:95% &
MPLAYER_PID=$!
fi
# ── Grabar cámara ────────────────────────────────────────────
if [ "$CAM_ENABLED" = "1" ]; then
ffmpeg -f v4l2 \
-video_size ${CAM_INPUT_W}x${CAM_INPUT_H} \
-framerate 30 \
-i /dev/video0 \
-c:v libx264 -preset ultrafast -crf 26 \
-tune zerolatency \
"$CAM_FILE" -y &
CAM_PID=$!
fi
# ── Grabar audio (monitor del sistema + micrófono mezclados) ─
MONITOR=$(pactl list sources short | grep monitor | head -1 | awk '{print $2}')
MIC=$(pactl list sources short | grep -v monitor | head -1 | awk '{print $2}')
echo "Monitor: $MONITOR"
echo "Micrófono: $MIC"
ffmpeg \
-f pulse -i "$MONITOR" \
-f pulse -i "$MIC" \
-filter_complex "[0:a][1:a]amix=inputs=2:duration=longest:dropout_transition=3[aout]" \
-map "[aout]" \
-acodec pcm_s16le \
"$AUDIO_FILE" -y &
AUDIO_PID=$!
# ── Grabar pantalla vía kmsgrab + VAAPI ──────────────────────
echo ""
echo "══════════════════════════════════════"
echo " ● Grabando — Ctrl+C para detener"
echo "══════════════════════════════════════"
sudo LIBVA_DRIVER_NAME=i965 ffmpeg \
-f kmsgrab -framerate 30 -device /dev/dri/card1 -i - \
-vaapi_device /dev/dri/renderD128 \
-vf "hwmap=derive_device=vaapi,\
scale_vaapi=w=${SCREEN_W}:h=${SCREEN_H}:format=nv12,\
hwdownload,format=nv12,\
crop=${CROP_W}:${CROP_H}:${CROP_X}:${CROP_Y},\
scale=${OUT_W}:${OUT_H},\
hwupload,scale_vaapi=format=nv12" \
-c:v h264_vaapi -global_quality 26 \
"$VIDEO_FILE" -y
#!/bin/bash
# Uso: ./own_obs.sh [x y w h]
# Sin argumentos: captura pantalla completa 1366x768 (16:9)
# Con argumentos: ./own_obs.sh 0 0 1366 768
#
# Extras disponibles (variables al inicio):
# SHOW_REC=1 → indicador ● REC
# CAM_ENABLED=1 → overlay de cámara (0 para desactivar)
# CAM_SIZE=320 → ancho del overlay de cámara en px
# INTRO_TEXT="" → texto de intro que aparece los primeros 3 seg
# WATERMARK="" → ruta a imagen PNG para watermark (ej: ~/logo.png)
set -euo pipefail
# ════════════════════════════════════════════════════
# CONFIGURACIÓN — editá estas variables
# ════════════════════════════════════════════════════
SHOW_REC=1
CAM_ENABLED=1
CAM_SIZE=280 # ancho del recuadro de cámara en px
INTRO_TEXT="" # ej: "Hola, bienvenidos al canal"
WATERMARK="" # ruta a logo PNG con alpha, o vacío para omitir
FONT=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf
SCREEN_W=1366
SCREEN_H=768
# Salida 16:9 — YouTube 1080p estándar
OUT_W=1280
OUT_H=720
# Cámara: forzamos 16:9 en captura para no distorsionar
CAM_INPUT_W=640
CAM_INPUT_H=360
# ════════════════════════════════════════════════════
# ARCHIVOS TEMPORALES
# ════════════════════════════════════════════════════
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
AUDIO_FILE="$HOME/audio_${TIMESTAMP}.wav"
VIDEO_FILE="$HOME/recording_${TIMESTAMP}.mp4"
CAM_FILE="$HOME/cam_${TIMESTAMP}.mp4"
OUTPUT_FILE="$HOME/final_${TIMESTAMP}.mp4"
# ════════════════════════════════════════════════════
# REGIÓN A CAPTURAR
# ════════════════════════════════════════════════════
if [ $# -eq 4 ]; then
CROP_X=$1; CROP_Y=$2; CROP_W=$3; CROP_H=$4
echo "Región manual: ${CROP_W}x${CROP_H} en offset ${CROP_X},${CROP_Y}"
else
CROP_X=0; CROP_Y=0
CROP_W=$SCREEN_W; CROP_H=$SCREEN_H
echo "Captura completa: ${CROP_W}x${CROP_H}"
fi
CROP_W=$(( CROP_W & ~1 ))
CROP_H=$(( CROP_H & ~1 ))
# ════════════════════════════════════════════════════
# PIDS
# ════════════════════════════════════════════════════
MPLAYER_PID=""; AUDIO_PID=""; CAM_PID=""
# ════════════════════════════════════════════════════
# SUBIDA A YOUTUBE
# ════════════════════════════════════════════════════
upload_youtube() {
local VIDEO_PATH="$1"
echo ""
read -r -p "¿Subir a YouTube? [s/N]: " RESPUESTA
if [[ ! "$RESPUESTA" =~ ^[sS]$ ]]; then
echo "Listo. Video guardado en: $VIDEO_PATH"
return
fi
read -r -p "Título: " YT_TITLE
read -r -p "Descripción (Enter para vacía): " YT_DESC
echo "Privacidad:"
echo " 1) Público"
echo " 2) No listado"
echo " 3) Privado"
read -r -p "Elegí [1-3] (default: 2): " YT_PRIV_OPT
case "$YT_PRIV_OPT" in
1) YT_PRIVACY="public" ;;
3) YT_PRIVACY="private" ;;
*) YT_PRIVACY="unlisted" ;;
esac
echo ""
echo "Subiendo '$YT_TITLE' como [$YT_PRIVACY]..."
source "$HOME/youtube-upload/bin/activate"
"$HOME/youtube-upload/youtube-upload/bin/youtube-upload" \
--title="$YT_TITLE" \
--description="$YT_DESC" \
--recording-date="$(date +%Y-%m-%dT%H:%M:%S.0Z)" \
--privacy="$YT_PRIVACY" \
--embeddable=True \
"$VIDEO_PATH"
if [ $? -eq 0 ]; then
echo "✓ Video subido exitosamente."
else
echo "✗ Error al subir. El video sigue en: $VIDEO_PATH"
fi
}
# ════════════════════════════════════════════════════
# CLEANUP + COMPOSICIÓN FINAL
# ════════════════════════════════════════════════════
cleanup() {
[ -n "$MPLAYER_PID" ] && kill "$MPLAYER_PID" 2>/dev/null || true
[ -n "$AUDIO_PID" ] && kill "$AUDIO_PID" 2>/dev/null || true
[ -n "$CAM_PID" ] && kill "$CAM_PID" 2>/dev/null || true
wait 2>/dev/null || true
# ── Verificar que existan los archivos necesarios ────────
MISSING=0
[ ! -f "$VIDEO_FILE" ] && echo "✗ Falta video de pantalla" && MISSING=1
[ ! -f "$AUDIO_FILE" ] && echo "✗ Falta audio" && MISSING=1
if [ "$CAM_ENABLED" = "1" ] && [ ! -f "$CAM_FILE" ]; then
echo "✗ Falta video de cámara" && MISSING=1
fi
[ "$MISSING" = "1" ] && exit 1
echo ""
echo "══════════════════════════════════════"
echo " Componiendo video final..."
echo "══════════════════════════════════════"
# ── Construir filter_complex dinámicamente ───────────────
# La lógica es:
# [0:v] → pantalla escalada → [screen]
# [1:v] → cámara escalada → [cam_scaled]
# si CAM_SHAPE=round → máscara circular → [cam_masked]
# overlay de cámara en esquina inferior derecha
# luego capas de texto: REC, reloj, intro, watermark
CAM_H=$(( CAM_SIZE * CAM_INPUT_H / CAM_INPUT_W ))
CAM_H=$(( CAM_H & ~1 ))
# padding de 20px desde los bordes
CAM_PAD=20
CAM_X=$(( OUT_W - CAM_SIZE - CAM_PAD ))
CAM_Y=$(( OUT_H - CAM_H - CAM_PAD ))
# ── Inputs de ffmpeg ─────────────────────────────────────
FFMPEG_INPUTS="-i $VIDEO_FILE"
if [ "$CAM_ENABLED" = "1" ]; then
FFMPEG_INPUTS="$FFMPEG_INPUTS -i $CAM_FILE"
fi
FFMPEG_INPUTS="$FFMPEG_INPUTS -i $AUDIO_FILE"
AUDIO_IDX=2
[ "$CAM_ENABLED" != "1" ] && AUDIO_IDX=1
# ── filter_complex ───────────────────────────────────────
FC="[0:v]scale=${OUT_W}:${OUT_H},setsar=1[screen];"
if [ "$CAM_ENABLED" = "1" ]; then
FC+="[1:v]scale=${CAM_SIZE}:${CAM_H},setsar=1[cam_scaled];"
FC+="[screen][cam_scaled]overlay=x=${CAM_X}:y=${CAM_Y}[with_cam];"
LAST="with_cam"
else
LAST="screen"
fi
# ── Capas de texto y decoración ──────────────────────────
TEXT_CHAIN="[${LAST}]"
# Indicador REC
if [ "$SHOW_REC" = "1" ]; then
REC_FILE=$(mktemp /tmp/rec_XXXXXX.txt)
printf '● REC' > "$REC_FILE"
TEXT_CHAIN+="drawbox=x=12:y=12:w=130:h=44:color=black@0.45:t=fill,"
TEXT_CHAIN+="drawtext=textfile=${REC_FILE}:fontcolor=red:fontsize=28:x=20:y=20:fontfile=${FONT},"
fi
# Intro text: aparece los primeros 3 segundos con fade out
if [ -n "$INTRO_TEXT" ]; then
TEXT_CHAIN+="drawtext=text=${INTRO_TEXT}:fontcolor=white:fontsize=42:"
TEXT_CHAIN+="x=(w-tw)/2:y=(h-th)/2:"
TEXT_CHAIN+="fontfile=${FONT}:"
TEXT_CHAIN+="shadowcolor=black:shadowx=3:shadowy=3:"
TEXT_CHAIN+="enable=between(t\,0\,3):"
TEXT_CHAIN+="alpha=if(lt(t\,2.5)\,1\,max(0\,1-(t-2.5)/0.5)),"
fi
# Quitar la coma final y cerrar con [out]
TEXT_CHAIN="${TEXT_CHAIN%,}[out]"
FC+="${TEXT_CHAIN}"
# Watermark (imagen PNG con alpha) — se agrega como input extra si se especifica
WM_INPUT=""
WM_EXTRA=""
if [ -n "$WATERMARK" ] && [ -f "$WATERMARK" ]; then
WM_INPUT="-i $WATERMARK"
# Reemplazar [out] final por composición con watermark
FC="${FC%\[out\]}"
WM_IDX=$(( AUDIO_IDX + 0 )) # viene después del audio
WM_INPUT="-i $WATERMARK"
FC+=";[out_pre][${WM_IDX}:v]overlay=x=w-overlay_w-20:y=h-overlay_h-20[out]"
AUDIO_IDX=$(( AUDIO_IDX + 1 ))
fi
# ── Ejecutar ffmpeg final ─────────────────────────────────
ffmpeg \
$FFMPEG_INPUTS \
$WM_INPUT \
-filter_complex "$FC" \
-map "[out]" -map "${AUDIO_IDX}:a" \
-c:v libx264 -preset fast -crf 23 \
-c:a aac -b:a 128k \
-movflags +faststart \
-shortest \
"$OUTPUT_FILE" -y
rm -f "$AUDIO_FILE" "$VIDEO_FILE" "$CAM_FILE" /tmp/rec_*.txt
echo ""
echo "══════════════════════════════════════"
echo " ✓ Guardado en: $OUTPUT_FILE"
SIZE=$(du -h "$OUTPUT_FILE" | cut -f1)
DUR=$(ffprobe -v error -show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 "$OUTPUT_FILE" 2>/dev/null \
| awk '{printf "%02d:%02d", $1/60, $1%60}')
echo " Tamaño: $SIZE Duración: ${DUR}"
echo "══════════════════════════════════════"
upload_youtube "$OUTPUT_FILE"
exit 0
}
trap 'cleanup' EXIT INT TERM
# ════════════════════════════════════════════════════
# INICIAR GRABACIONES
# ════════════════════════════════════════════════════
# ── Configurar cámara ────────────────────────────────────────
v4l2-ctl --device=/dev/video0 \
--set-fmt-video=width=${CAM_INPUT_W},height=${CAM_INPUT_H},pixelformat=YUYV
# ── Preview cámara en framebuffer ────────────────────────────
if [ "$CAM_ENABLED" = "1" ]; then
mplayer -vo fbdev2 \
-tv driver=v4l2:device=/dev/video0:width=${CAM_INPUT_W}:height=${CAM_INPUT_H} tv:// \
-zoom -geometry 95%:95% &
MPLAYER_PID=$!
fi
# ── Grabar cámara ────────────────────────────────────────────
if [ "$CAM_ENABLED" = "1" ]; then
ffmpeg -f v4l2 \
-video_size ${CAM_INPUT_W}x${CAM_INPUT_H} \
-framerate 30 \
-i /dev/video0 \
-c:v libx264 -preset ultrafast -crf 26 \
-tune zerolatency \
"$CAM_FILE" -y &
CAM_PID=$!
fi
# ── Grabar audio (monitor del sistema + micrófono mezclados) ─
MONITOR=$(pactl list sources short | grep monitor | head -1 | awk '{print $2}')
MIC=$(pactl list sources short | grep -v monitor | head -1 | awk '{print $2}')
echo "Monitor: $MONITOR"
echo "Micrófono: $MIC"
ffmpeg \
-f pulse -i "$MONITOR" \
-f pulse -i "$MIC" \
-filter_complex "[0:a][1:a]amix=inputs=2:duration=longest:dropout_transition=3[aout]" \
-map "[aout]" \
-acodec pcm_s16le \
"$AUDIO_FILE" -y &
AUDIO_PID=$!
# ── Grabar pantalla vía kmsgrab + VAAPI ──────────────────────
echo ""
echo "══════════════════════════════════════"
echo " ● Grabando — Ctrl+C para detener"
echo "══════════════════════════════════════"
sudo LIBVA_DRIVER_NAME=i965 ffmpeg \
-f kmsgrab -framerate 30 -device /dev/dri/card1 -i - \
-vaapi_device /dev/dri/renderD128 \
-vf "hwmap=derive_device=vaapi,\
scale_vaapi=w=${SCREEN_W}:h=${SCREEN_H}:format=nv12,\
hwdownload,format=nv12,\
crop=${CROP_W}:${CROP_H}:${CROP_X}:${CROP_Y},\
scale=${OUT_W}:${OUT_H},\
hwupload,scale_vaapi=format=nv12" \
-c:v h264_vaapi -global_quality 26 \
"$VIDEO_FILE" -y