# creator -- content creation aliases and functions (recording, streaming, editing)
# Sources: BreadOnPenguins, LukeSmithxyz, mochou-p, NapoleonWils0n, community
# ============================================================================
# RECORDING
# ============================================================================
alias recstatus='cat /tmp/recordingicon 2>/dev/null || echo "not recording"'
# ============================================================================
# OBS CLI (requires obs-cli / obs-websocket)
# ============================================================================
alias obs-stream-start='obs-cli stream start'
alias obs-stream-stop='obs-cli stream stop'
alias obs-stream-toggle='obs-cli stream toggle'
alias obs-stream-status='obs-cli stream status'
alias obs-rec-start='obs-cli recording start'
alias obs-rec-stop='obs-cli recording stop'
alias obs-rec-toggle='obs-cli recording toggle'
alias obs-rec-status='obs-cli recording status'
# ============================================================================
# YT-DLP ARCHIVING
# ============================================================================
alias ytbest='yt-dlp -f "bestvideo+bestaudio" --merge-output-format mkv'
alias ytarchive='yt-dlp -f "bestvideo+bestaudio" --merge-output-format mkv --embed-subs --embed-thumbnail --embed-metadata --embed-chapters --write-info-json'
alias ytsubs='yt-dlp --write-sub --write-auto-sub --sub-lang en --convert-subs srt'
# ============================================================================
# GIF / SOCIAL
# ============================================================================
alias togif='f() { palette="/tmp/palette.png"; ffmpeg -v warning -i "$1" -vf "fps=7,scale=480:-1:flags=lanczos,palettegen" -y "$palette" && ffmpeg -v warning -i "$1" -i "$palette" -lavfi "fps=7,scale=480:-1:flags=lanczos [x]; [x][1:v] paletteuse" -y "${2:-output.gif}"; }; f'
# ============================================================================
# TWITCH
# ============================================================================
alias twitchdl='twitch-dl download'
alias twitchclips='twitch-dl clips'
alias twitchvods='twitch-dl videos'
# ============================================================================
# AUDIO QUICK
# ============================================================================
alias normalize='ffmpeg-normalize'
# ============================================================================
# FUNCTIONS
# ============================================================================
# toggle screen+audio recording with statusbar indicator
record_toggle() {
if [ -f /tmp/recpid ]; then
kill -15 "$(cat /tmp/recpid)" "$(cat /tmp/audpid 2>/dev/null)" 2>/dev/null
rm -f /tmp/recpid /tmp/audpid
echo "" > /tmp/recordingicon
pkill -RTMIN+3 dwmblocks 2>/dev/null
notify-send "Recording ended" 2>/dev/null
else
res=$(xdpyinfo 2>/dev/null | awk '/dimensions/{print $2}')
[ -z "$res" ] && res="1920x1080"
ffmpeg -s "$res" -f x11grab -r 30 -i :0.0 \
-c:v h264 -qp 0 "$HOME/video_$(date '+%Y%m%d_%H%M%S').mkv" &
echo $! > /tmp/recpid
ffmpeg -f alsa -i default -af "afftdn=nf=-75" \
"$HOME/audio_$(date '+%Y%m%d_%H%M%S').wav" &
echo $! > /tmp/audpid
echo "REC" > /tmp/recordingicon
pkill -RTMIN+3 dwmblocks 2>/dev/null
notify-send "Recording started" 2>/dev/null
fi
}
# dmenu-driven recording mode selector
record_menu() {
getdim() { xdpyinfo | awk '/dimensions/{print $2}'; }
choice=$(printf "screencast\nvideo\nvideo selected\naudio\nwebcam" \
| dmenu -i -p "Record:")
case "$choice" in
screencast)
ffmpeg -y -f x11grab -framerate 30 -s "$(getdim)" -i "$DISPLAY" \
-r 24 -f alsa -thread_queue_size 1024 -i default \
-c:v h264 -crf 0 -preset ultrafast -c:a aac \
"$HOME/screencast-$(date '+%y%m%d-%H%M-%S').mp4" &
echo $! > /tmp/recordingpid ;;
video)
ffmpeg -f x11grab -framerate 30 -s "$(getdim)" -i "$DISPLAY" \
-c:v libx264 -qp 0 -r 30 \
"$HOME/video-$(date '+%y%m%d-%H%M-%S').mkv" &
echo $! > /tmp/recordingpid ;;
"video selected")
slop -f "%x %y %w %h" > /tmp/slop
read X Y W H < /tmp/slop
ffmpeg -f x11grab -framerate 30 -video_size "${W}x${H}" -i :0.0+"$X,$Y" \
-c:v libx264 -qp 0 -r 30 "$HOME/box-$(date '+%y%m%d-%H%M-%S').mkv" &
echo $! > /tmp/recordingpid ;;
audio)
ffmpeg -f alsa -i default -c:a flac \
"$HOME/audio-$(date '+%y%m%d-%H%M-%S').flac" &
echo $! > /tmp/recordingpid ;;
webcam)
ffmpeg -f v4l2 -i /dev/video0 -video_size 640x480 \
"$HOME/webcam-$(date '+%y%m%d-%H%M-%S').mkv" &
echo $! > /tmp/recordingpid ;;
esac
}
# stop any active recording
record_stop() {
[ -f /tmp/recordingpid ] && kill -15 "$(cat /tmp/recordingpid)" && rm /tmp/recordingpid
[ -f /tmp/recpid ] && kill -15 "$(cat /tmp/recpid)" && rm /tmp/recpid /tmp/audpid 2>/dev/null
echo "" > /tmp/recordingicon
pkill -RTMIN+3 dwmblocks 2>/dev/null
}
# trim video by start time and duration
trimvid() {
input="$1"; ss="$2"; dur="$3"; output="${4:-${input%.*}_trimmed.${input##*.}}"
[ -z "$dur" ] && { echo "Usage: trimvid <input> <start> <duration> [output]"; return 1; }
ffmpeg -ss "$ss" -t "$dur" -i "$input" -c copy "$output"
}
# concatenate videos from a list
concatvids() {
listfile="$1"; output="${2:-concat_output.mkv}"
[ -z "$listfile" ] && { echo "Usage: concatvids <list.txt> [output]"; return 1; }
ffmpeg -f concat -safe 0 -i "$listfile" -c copy "$output"
}
# picture-in-picture overlay
pip_overlay() {
bg="$1"; fg="$2"; output="${3:-pip_output.mkv}"; pos="${4:-br}"; width="${5:-480}"
[ -z "$fg" ] && { echo "Usage: pip_overlay <background> <foreground> [output] [tl|tr|bl|br] [width]"; return 1; }
case "$pos" in
tl) x="10"; y="10" ;;
tr) x="main_w-overlay_w-10"; y="10" ;;
bl) x="10"; y="main_h-overlay_h-10" ;;
br) x="main_w-overlay_w-10"; y="main_h-overlay_h-10" ;;
esac
ffmpeg -i "$bg" -i "$fg" -filter_complex \
"[1:v]scale=$width:-1[pip];[0:v][pip]overlay=$x:$y" \
-c:a copy "$output"
}
# fade video in/out
fadevid() {
input="$1"; duration="${2:-0.5}"; output="${3:-${input%.*}_faded.${input##*.}}"
[ -z "$input" ] && { echo "Usage: fadevid <input> [duration] [output]"; return 1; }
total=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$input" | cut -d. -f1)
fadeout=$((total - ${duration%.*}))
ffmpeg -i "$input" -vf "fade=t=in:st=0:d=$duration,fade=t=out:st=$fadeout:d=$duration" \
-af "afade=t=in:st=0:d=$duration,afade=t=out:st=$fadeout:d=$duration" "$output"
}
# split video for YouTube 15-min limit
split_for_yt() {
input="$1"; chunk="${2:-900}"
[ -z "$input" ] && { echo "Usage: split_for_yt <video> [chunk-seconds]"; return 1; }
base="${input%.*}"; ext="${input##*.}"
dur=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$input" | cut -d. -f1)
i=0; part=1
while [ "$i" -lt "$dur" ]; do
ffmpeg -ss "$i" -t "$chunk" -i "$input" -c copy "${base}_part$(printf '%03d' $part).${ext}"
i=$((i + chunk)); part=$((part + 1))
done
}
# seamless loop creator
loopvid() {
input="$1"; crossfade="${2:-5}"
[ -z "$input" ] && { echo "Usage: loopvid <video> [crossfade-sec]"; return 1; }
len=$(ffprobe -v error -select_streams v:0 -show_entries stream=duration \
-of default=nw=1:nk=1 "$input" | cut -d. -f1)
outlen=$((len - crossfade))
out=$((len - crossfade - crossfade))
ffmpeg -ss "$crossfade" -i "$input" -i "$input" -filter_complex \
"[0:v]fade=t=out:st=$out:d=$crossfade:alpha=1,setpts=PTS-STARTPTS[va0]; \
[1:v]fade=t=in:st=0:d=$crossfade:alpha=1,setpts=PTS-STARTPTS+$out/TB[va1]; \
[va0][va1]overlay[outv]" -map "[outv]" -to "$outlen" "loop_${input}"
}
# noise reduction (sox + ffmpeg)
noisereduce() {
input="$1"; output="${2:-${input%.*}_clean.${input##*.}}"
[ -z "$input" ] && { echo "Usage: noisereduce <input> [output]"; return 1; }
echo "Extracting audio..."
ffmpeg -y -i "$input" -qscale:a 0 /tmp/nr_audio.wav 2>/dev/null
echo "Sample noise: enter start time (e.g. 0.5):"
read ns
echo "Sample end time:"
read ne
ffmpeg -y -i "$input" -vn -ss "$ns" -t "$ne" /tmp/nr_noise.wav 2>/dev/null
sox /tmp/nr_noise.wav -n noiseprof /tmp/nr_profile
echo "Sensitivity (default 0.21):"
read sens
[ -z "$sens" ] && sens="0.21"
sox /tmp/nr_audio.wav /tmp/nr_clean.wav noisered /tmp/nr_profile "$sens"
ffmpeg -y -i "$input" -an -c:v copy /tmp/nr_video.mkv 2>/dev/null
ffmpeg -y -i /tmp/nr_clean.wav -i /tmp/nr_video.mkv -c:v copy "$output" 2>/dev/null
rm -f /tmp/nr_audio.wav /tmp/nr_noise.wav /tmp/nr_profile /tmp/nr_clean.wav /tmp/nr_video.mkv
echo "[+] Cleaned: $output"
}
# screenshot with optional watermark
screenshot() {
mode="${1:-selection}"
file="$HOME/Pictures/screenshots/$(date '+%Y%m%d_%H%M%S').png"
mkdir -p "$(dirname "$file")"
case "$mode" in
full) maim -q -d 0.2 "$file" ;;
window) maim -B -q -d 0.2 -i "$(xdotool getactivewindow)" "$file" ;;
selection) maim -u -s "$file" ;;
ocr) tmpf=$(mktemp /tmp/ocr-XXXXXX.png); maim -u -s > "$tmpf" && \
tesseract "$tmpf" - -l eng | xclip -sel clip && rm "$tmpf"; return ;;
esac
xclip -selection clipboard -t image/png -i "$file"
notify-send "Screenshot" "$(basename "$file")" 2>/dev/null
}
# ImageMagick batch watermark
imgwatermark() {
wm="$1"; shift
[ -z "$wm" ] && { echo "Usage: imgwatermark <watermark.png> <images...>"; return 1; }
for img in "$@"; do
composite -compose overlay -gravity center -dissolve 40% "$wm" "$img" "${img%.*}_wm.${img##*.}"
done
}
# YouTube live stream via ffmpeg
ytlive() {
streamkey="$1"
[ -z "$streamkey" ] && { echo "Usage: ytlive <stream-key>"; return 1; }
url="rtmp://a.rtmp.youtube.com/live2"
res=$(xdpyinfo 2>/dev/null | awk '/dimensions/{print $2}')
[ -z "$res" ] && res="1920x1080"
ffmpeg -f x11grab -framerate 30 -s "$res" -i "$DISPLAY" \
-f alsa -thread_queue_size 1024 -i default \
-c:v libx264 -b:v 4500k -preset veryfast -maxrate 4500k -bufsize 9000k \
-vf "format=yuv420p" -g 60 \
-c:a aac -b:a 128k -ar 44100 \
-f flv "$url/$streamkey"
}
# upload to YouTube (requires youtube-upload CLI)
ytupload() {
video="$1"; title="$2"
[ -z "$title" ] && { echo "Usage: ytupload <video> <title> [description]"; return 1; }
desc="${3:-Uploaded via CLI}"
youtube-upload \
--title="$title" \
--description="$desc" \
--category="Science & Technology" \
--privacy=private \
"$video"
}
# archive a channel with yt-dlp
ytchannel_archive() {
[ -z "$1" ] && { echo "Usage: ytchannel_archive <channel-url>"; return 1; }
yt-dlp \
-f "bestvideo[height>=1080]+bestaudio/bestvideo+bestaudio/best" \
--merge-output-format mkv \
--embed-subs --embed-thumbnail --embed-metadata --embed-chapters \
--write-info-json --write-description --write-thumbnail \
--download-archive archive.txt \
-o "%(uploader)s/%(upload_date)s - %(title)s [%(id)s].%(ext)s" \
--sponsorblock-mark all "$1"
}
# audio output switcher via dmenu
audioswitch() {
options=$(pactl -f json list sinks | jq -r '.[] | .description')
selection=$(echo "$options" | dmenu -i -l 5 -c -p "Output:")
[ -z "$selection" ] && return
sink=$(pactl -f json list sinks | jq -r --arg d "$selection" \
'.[] | select(.description == $d) | .name')
[ -n "$sink" ] && pactl set-default-sink "$sink" && \
notify-send "Audio: $selection" 2>/dev/null
}
# Discord webhook notification (requires DISCORD_WEBHOOK env var)
discord_notify() {
msg="$1"
[ -z "$msg" ] && { echo "Usage: discord_notify <message>"; return 1; }
[ -z "$DISCORD_WEBHOOK" ] && { echo "Set DISCORD_WEBHOOK first"; return 1; }
curl -s -H "Content-Type: application/json" \
-d "{\"content\":\"$msg\"}" "$DISCORD_WEBHOOK"
}
# countdown timer with statusbar integration
creator_timer() {
minutes="${1:-25}"
total=$((minutes * 60))
while [ "$total" -gt 0 ]; do
m=$(printf "%02d" $((total / 60)))
s=$(printf "%02d" $((total % 60)))
printf "\r %s:%s " "$m" "$s"
echo " $m:$s " > /tmp/timericon 2>/dev/null
pkill -RTMIN+3 dwmblocks 2>/dev/null
sleep 1
total=$((total - 1))
done
printf "\n"
notify-send "Timer done" "Take a break" 2>/dev/null
echo "" > /tmp/timericon 2>/dev/null
pkill -RTMIN+3 dwmblocks 2>/dev/null
}
# PipeWire loopback mixing system audio + mic
pw_mix_setup() {
loopback="in_and_out_mix"
default_sink=$(pw-metadata 2>/dev/null | grep 'default.audio.sink' | grep -oP '(?<="name":")[^"]+')
default_source=$(pw-metadata 2>/dev/null | grep 'default.audio.source' | grep -oP '(?<="name":")[^"]+')
pw-loopback --name="$loopback" --channels=2 \
--capture-props="media.class=Audio/Sink node.name=${loopback}_in" \
--playback-props="media.class=Audio/Source node.name=${loopback}_out" &
sleep 1
pw-link "${default_sink}:monitor_FL" "${loopback}_in:playback_FL" 2>/dev/null
pw-link "${default_sink}:monitor_FR" "${loopback}_in:playback_FR" 2>/dev/null
source_port=$(pw-link -o | grep "$default_source" | head -1 | cut -d: -f2-)
pw-link "${default_source}:${source_port}" "${loopback}_in:playback_FL" 2>/dev/null
echo "[+] PipeWire mix ready: ${loopback}_out"
}
# display selector via dmenu
displayselect() {
screens=$(xrandr -q | awk '/ connected/ {print $1}')
chosen=$(printf "%s\nmulti-monitor\nmanual" "$screens" | dmenu -i -p "Display:")
[ -z "$chosen" ] && return
if [ "$chosen" = "manual" ]; then
arandr &
elif [ "$chosen" = "multi-monitor" ]; then
xrandr --auto
else
xrandr --output "$chosen" --auto --output "$(echo "$screens" | grep -v "$chosen" | head -1)" --off
fi
}