mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2025-12-07 22:48:00 -08:00
Compare commits
4 Commits
d8a0e95bb5
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
33b541dbd8
|
|||
|
a93761b042
|
|||
|
a1ec1a54ba
|
|||
|
|
7b7fae9b91 |
@@ -26,7 +26,6 @@ monitorv2 {
|
|||||||
scale = 1
|
scale = 1
|
||||||
vrr = 2
|
vrr = 2
|
||||||
cm = srgb
|
cm = srgb
|
||||||
|
|
||||||
# Optional HDR settings
|
# Optional HDR settings
|
||||||
# cm = hdr
|
# cm = hdr
|
||||||
# bitdepth = 10
|
# bitdepth = 10
|
||||||
|
|||||||
@@ -139,3 +139,5 @@ bind = $mainMod, code:112, submap, reset
|
|||||||
submap = reset
|
submap = reset
|
||||||
|
|
||||||
bind = SUPER, l, exec, hyprlock
|
bind = SUPER, l, exec, hyprlock
|
||||||
|
|
||||||
|
bind = $mainMod SHIFT, a, exec, ~/.config/rofi/scripts/rofi-anki-script.sh
|
||||||
|
|||||||
@@ -436,6 +436,14 @@ local misc_utilities_mappings = {
|
|||||||
end,
|
end,
|
||||||
group = "mkdir under cursor",
|
group = "mkdir under cursor",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
mode = "v",
|
||||||
|
key = "<leader>m",
|
||||||
|
cmd = function()
|
||||||
|
mkdir_under_cursor()
|
||||||
|
end,
|
||||||
|
group = "mkdir selection",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,20 @@ local M = {}
|
|||||||
vim.notify = require("notify")
|
vim.notify = require("notify")
|
||||||
|
|
||||||
function M.mkdir_under_cursor()
|
function M.mkdir_under_cursor()
|
||||||
-- Get the word under the cursor
|
local word
|
||||||
local word = vim.fn.expand("<cWORD>")
|
|
||||||
|
-- Check if in visual mode
|
||||||
|
if vim.fn.mode():match("[vV]") then
|
||||||
|
-- Get visual selection
|
||||||
|
local start_pos = vim.fn.getpos("'<")
|
||||||
|
local end_pos = vim.fn.getpos("'>")
|
||||||
|
local line = vim.fn.getline(start_pos[2])
|
||||||
|
word = line:sub(start_pos[3], end_pos[3])
|
||||||
|
else
|
||||||
|
-- Get word under cursor
|
||||||
|
word = vim.fn.expand("<cWORD>")
|
||||||
|
end
|
||||||
|
|
||||||
-- Remove quotes if present
|
-- Remove quotes if present
|
||||||
word = word:gsub("^[\"']", ""):gsub("[\"']$", "")
|
word = word:gsub("^[\"']", ""):gsub("[\"']$", "")
|
||||||
-- Check if directory exists
|
-- Check if directory exists
|
||||||
|
|||||||
@@ -10,8 +10,10 @@
|
|||||||
"Cloudflare - https://dash.cloudflare.com/",
|
"Cloudflare - https://dash.cloudflare.com/",
|
||||||
"CoinMarketCap - https://coinmarketcap.com/",
|
"CoinMarketCap - https://coinmarketcap.com/",
|
||||||
"Deemix - http://pve-main:3358",
|
"Deemix - http://pve-main:3358",
|
||||||
|
"Ephemera - https://ephemera.suda.codes",
|
||||||
"F1TV - https://f1tv.suda.codes",
|
"F1TV - https://f1tv.suda.codes",
|
||||||
"Fidelity - https://login.fidelity.com/",
|
"Fidelity - https://login.fidelity.com/",
|
||||||
|
"Ghstats - http://oracle-vm:3340",
|
||||||
"Gitea - https://gitea.suda.codes",
|
"Gitea - https://gitea.suda.codes",
|
||||||
"Github - https://github.com",
|
"Github - https://github.com",
|
||||||
"Ghostfolio - http://pve-main:3334",
|
"Ghostfolio - http://pve-main:3334",
|
||||||
@@ -23,7 +25,7 @@
|
|||||||
"Jellyfin (Vue) - http://pve-main:8098",
|
"Jellyfin (Vue) - http://pve-main:8098",
|
||||||
"Karakeep - https://karakeep.suda.codes",
|
"Karakeep - https://karakeep.suda.codes",
|
||||||
"Komodo - https://komodo.suda.codes",
|
"Komodo - https://komodo.suda.codes",
|
||||||
"Komga - http://oracle-vm:3332",
|
"Komga - http://pve-main:3332",
|
||||||
"Lidarr - http://pve-main:3357",
|
"Lidarr - http://pve-main:3357",
|
||||||
"MeTube - https://metube.suda.codes",
|
"MeTube - https://metube.suda.codes",
|
||||||
"Navidrome - https://navidrome.suda.codes",
|
"Navidrome - https://navidrome.suda.codes",
|
||||||
@@ -32,7 +34,8 @@
|
|||||||
"Pihole - https://pihole.suda.codes/admin",
|
"Pihole - https://pihole.suda.codes/admin",
|
||||||
"Pihole2 - https://pihole2.suda.codes/admin",
|
"Pihole2 - https://pihole2.suda.codes/admin",
|
||||||
"Proxmox - https://thebox.unicorn-ilish.ts.net",
|
"Proxmox - https://thebox.unicorn-ilish.ts.net",
|
||||||
"qBittorrent - https://qbit.suda.codes",
|
"qBittorrent - https://qbittorrent.suda.codes",
|
||||||
|
"qui - https://qui.suda.codes",
|
||||||
"Plausible - https://plausible.sudacode.com",
|
"Plausible - https://plausible.sudacode.com",
|
||||||
"Paperless - https://paperless.suda.codes",
|
"Paperless - https://paperless.suda.codes",
|
||||||
"Prometheus - http://prometheus:9090",
|
"Prometheus - http://prometheus:9090",
|
||||||
@@ -42,6 +45,7 @@
|
|||||||
"Sabnzbd - https://sabnzbd.suda.codes",
|
"Sabnzbd - https://sabnzbd.suda.codes",
|
||||||
"Sonarr - https://sonarr.suda.codes",
|
"Sonarr - https://sonarr.suda.codes",
|
||||||
"Sonarr Anime - http://pve-main:6969",
|
"Sonarr Anime - http://pve-main:6969",
|
||||||
|
"Speedtest Tracker - http://pve-main:8765",
|
||||||
"Sudacode - https://sudacode.com",
|
"Sudacode - https://sudacode.com",
|
||||||
"Suwayomi - https://suwayomi.suda.codes",
|
"Suwayomi - https://suwayomi.suda.codes",
|
||||||
"Tailscale - https://login.tailscale.com/admin/machines",
|
"Tailscale - https://login.tailscale.com/admin/machines",
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR/rofi-menu-helpers.sh"
|
||||||
|
|
||||||
BROWSER=/usr/bin/zen-browser
|
BROWSER=/usr/bin/zen-browser
|
||||||
OPTIONS=(
|
DOC_GROUPS=(
|
||||||
"Arch Linux (btw)"
|
"Arch Linux (btw)|ARCH"
|
||||||
"Hyprland"
|
"Hyprland|HYPRLAND"
|
||||||
)
|
)
|
||||||
ARCH=(
|
ARCH=(
|
||||||
"Archlinux Wiki|https://wiki.archlinux.org/title/Main_page"
|
"Archlinux Wiki|https://wiki.archlinux.org/title/Main_page"
|
||||||
@@ -13,56 +16,37 @@ HYPRLAND=(
|
|||||||
"Hyprland Window Rules|https://wiki.hypr.land/Configuring/Window-Rules/"
|
"Hyprland Window Rules|https://wiki.hypr.land/Configuring/Window-Rules/"
|
||||||
)
|
)
|
||||||
|
|
||||||
get_url() {
|
select_group() {
|
||||||
urls=("$@")
|
rofi_select_label_value "Select Documentation Group" DOC_GROUPS
|
||||||
display_urls=()
|
|
||||||
declare -A url_map
|
|
||||||
for url in "${urls[@]}"; do
|
|
||||||
display_urls+=("${url%%|*}")
|
|
||||||
label="${url%%|*}"
|
|
||||||
url_map["$label"]="${url##*|}"
|
|
||||||
done
|
|
||||||
display_urls+=("Back")
|
|
||||||
url_map["Back"]="Back"
|
|
||||||
|
|
||||||
selection="$(printf "%s\n" "${display_urls[@]}" | rofi -theme-str 'window {width: 25%;} listview {columns: 1; lines: 10;}' -theme ~/.config/rofi/launchers/type-2/style-2.rasi -dmenu -l 5 -i -p "Select Documentation")"
|
|
||||||
url="${url_map[$selection]}"
|
|
||||||
|
|
||||||
if [ -z "$url" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf "%s\n" "$url"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get_docs_list() {
|
select_url() {
|
||||||
selection="$(printf "%s\n" "${OPTIONS[@]}" | rofi -theme-str 'window {width: 25%;} listview {columns: 1; lines: 10;}' -theme ~/.config/rofi/launchers/type-2/style-2.rasi -dmenu -l 5 -i -p "Select Documentation Group")"
|
local urls_array="$1"
|
||||||
case "$selection" in
|
rofi_select_label_value "Select Documentation" "$urls_array" "Back"
|
||||||
"Arch Linux (btw)")
|
|
||||||
urls=("${ARCH[@]}")
|
|
||||||
;;
|
|
||||||
"Hyprland")
|
|
||||||
urls=("${HYPRLAND[@]}")
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
printf "%s\n" "${urls[@]}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
mapfile -t urls < <(get_docs_list)
|
while true; do
|
||||||
url="$(get_url "${urls[@]}")"
|
group_key="$(select_group)" || exit 0
|
||||||
if [ -z "$url" ]; then
|
case "$group_key" in
|
||||||
printf "No URL selected.\n"
|
ARCH)
|
||||||
|
urls_ref=ARCH
|
||||||
|
;;
|
||||||
|
HYPRLAND)
|
||||||
|
urls_ref=HYPRLAND
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
selection="$(select_url "$urls_ref")" || exit 0
|
||||||
|
if [[ "$selection" == "Back" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
$BROWSER "$selection" &>/dev/null &
|
||||||
exit 0
|
exit 0
|
||||||
elif [ "$url" == "Back" ]; then
|
done
|
||||||
main
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
$BROWSER "$url" &>/dev/null &
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main
|
main
|
||||||
|
|||||||
68
.config/rofi/scripts/rofi-menu-helpers.sh
Normal file
68
.config/rofi/scripts/rofi-menu-helpers.sh
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Lightweight helpers to build rofi menus with label/value pairs.
|
||||||
|
# Intended to be sourced from other scripts.
|
||||||
|
|
||||||
|
# Allow callers to override theme/args without touching code.
|
||||||
|
: "${ROFI_THEME:=$HOME/.config/rofi/launchers/type-2/style-2.rasi}"
|
||||||
|
: "${ROFI_THEME_STR:="window {width: 25%;} listview {columns: 1; lines: 10;}"}"
|
||||||
|
: "${ROFI_DMENU_ARGS:=-i -l 5}"
|
||||||
|
|
||||||
|
# rofi_menu prompt option...
|
||||||
|
# Prints the selected option to stdout and propagates the rofi exit code
|
||||||
|
# (1 when the user cancels).
|
||||||
|
rofi_menu() {
|
||||||
|
local prompt="$1"
|
||||||
|
shift
|
||||||
|
local -a options=("$@")
|
||||||
|
|
||||||
|
local selection
|
||||||
|
selection="$(printf "%s\n" "${options[@]}" | rofi -dmenu $ROFI_DMENU_ARGS \
|
||||||
|
${ROFI_THEME:+-theme "$ROFI_THEME"} \
|
||||||
|
${ROFI_THEME_STR:+-theme-str "$ROFI_THEME_STR"} \
|
||||||
|
-p "$prompt")"
|
||||||
|
local status=$?
|
||||||
|
[[ $status -ne 0 ]] && return "$status"
|
||||||
|
printf "%s\n" "$selection"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rofi_select_label_value prompt array_name [back_label]
|
||||||
|
# array_name should contain entries shaped as "Label|Value".
|
||||||
|
# Prints the mapped value (or the back label when chosen). Returns 1 on cancel.
|
||||||
|
rofi_select_label_value() {
|
||||||
|
local prompt="$1"
|
||||||
|
local array_name="$2"
|
||||||
|
local back_label="${3:-}"
|
||||||
|
|
||||||
|
# Access caller's array by name
|
||||||
|
local -n kv_source="$array_name"
|
||||||
|
local -A kv_map=()
|
||||||
|
local -a display=()
|
||||||
|
|
||||||
|
for entry in "${kv_source[@]}"; do
|
||||||
|
local label="${entry%%|*}"
|
||||||
|
local value="${entry#*|}"
|
||||||
|
kv_map["$label"]="$value"
|
||||||
|
display+=("$label")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n "$back_label" ]]; then
|
||||||
|
kv_map["$back_label"]="$back_label"
|
||||||
|
display+=("$back_label")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local selection
|
||||||
|
selection="$(rofi_menu "$prompt" "${display[@]}")" || return "$?"
|
||||||
|
[[ -z "$selection" ]] && return 1
|
||||||
|
printf "%s\n" "${kv_map[$selection]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rofi_select_list prompt array_name
|
||||||
|
# Convenience wrapper for plain lists (no label/value mapping).
|
||||||
|
rofi_select_list() {
|
||||||
|
local prompt="$1"
|
||||||
|
local array_name="$2"
|
||||||
|
local -n list_source="$array_name"
|
||||||
|
rofi_menu "$prompt" "${list_source[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
226
projects/scripts/record-audio.sh
Normal file → Executable file
226
projects/scripts/record-audio.sh
Normal file → Executable file
@@ -1,93 +1,145 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Version 1.2
|
# Toggle desktop audio recording and attach the result to the newest Anki note
|
||||||
# shoutout to https://gist.github.com/Cephian/f849e326e3522be9a4386b60b85f2f23 for the original script,
|
# (as tagged by Yomichan). Run once to start recording, run again to stop.
|
||||||
# https://github.com/xythh/ added the ankiConnect functionality
|
# Dependencies: jq, curl, ffmpeg/ffprobe, pulseaudio (parec+pactl), bc, notify-send
|
||||||
# toggle record computer audio (run once to start, run again to stop)
|
|
||||||
# dependencies: ffmpeg, pulseaudio, curl
|
|
||||||
|
|
||||||
# where recording gets saved, gets deleted after being imported to anki
|
set -euo pipefail
|
||||||
DIRECTORY="$HOME/.cache/"
|
|
||||||
FORMAT="mp3" # ogg or mp3
|
|
||||||
# cut file since it glitches a bit at the end sometimes
|
|
||||||
CUT_DURATION="0.1"
|
|
||||||
#port used by ankiconnect
|
|
||||||
ankiConnectPort="8765"
|
|
||||||
# gets the newest created card, so make sure to create the card first with yomichan
|
|
||||||
newestNoteId=$(curl -s localhost:$ankiConnectPort -X POST -d '{"action": "findNotes", "version": 6, "params": { "query": "is:new"}}' | jq '.result[-1]')
|
|
||||||
#Audio field name
|
|
||||||
audioFieldName="SentenceAudio"
|
|
||||||
|
|
||||||
#if there is no newest note, you either have a complete empty anki or ankiconnect isn't running
|
ANKI_CONNECT_PORT="${ANKI_CONNECT_PORT:-8765}"
|
||||||
if [ "$newestNoteId" = "" ]; then
|
AUDIO_FIELD_NAME="${AUDIO_FIELD_NAME:-SentenceAudio}"
|
||||||
notify-send "anki connect not found"
|
FORMAT="${FORMAT:-mp3}" # mp3 or ogg
|
||||||
exit 1
|
CUT_DURATION="${CUT_DURATION:-0.1}"
|
||||||
fi
|
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/record-audio"
|
||||||
|
RECORD_TIMEOUT="${RECORD_TIMEOUT:-60}"
|
||||||
|
ANKI_URL="http://localhost:${ANKI_CONNECT_PORT}"
|
||||||
|
|
||||||
if pgrep -f "parec"; then
|
require_cmd() {
|
||||||
pkill -f "parec"
|
command -v "$1" >/dev/null 2>&1 || {
|
||||||
else
|
echo "Missing dependency: $1" >&2
|
||||||
time=$(date +%s)
|
exit 1
|
||||||
name="$DIRECTORY/$time"
|
|
||||||
wav_file="$name.wav"
|
|
||||||
out_file="$name.$FORMAT"
|
|
||||||
|
|
||||||
if ! [ -d "$DIRECTORY" ]; then
|
|
||||||
mkdir "$DIRECTORY"
|
|
||||||
fi
|
|
||||||
notify-send -t 1000 "Audio recording started"
|
|
||||||
#timeout 1m arecord -t wav -f cd "$wav_file"
|
|
||||||
|
|
||||||
# just grabs last running source... may not always work if your pulseaudio setup is complicated
|
|
||||||
if ! timeout 1m parec -d"$(pactl list sinks | grep -B1 'State: RUNNING' | sed -nE 's/Sink #(.*)/\1/p' | tail -n 1)" --file-format=wav "$wav_file"; then
|
|
||||||
|
|
||||||
notify-send "Error recording " "most likely no audio playing"
|
|
||||||
rm "$wav_file"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
input_duration=$(ffprobe -v error -select_streams a:0 -show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 "$wav_file")
|
|
||||||
output_duration=$(echo "$input_duration"-"$CUT_DURATION" | bc)
|
|
||||||
|
|
||||||
# encode file and delete OG
|
|
||||||
if [ $FORMAT = "ogg" ]; then
|
|
||||||
ffmpeg -i "$wav_file" -vn -codec:a libvorbis -b:a 64k -t "$output_duration" "$out_file"
|
|
||||||
elif [ $FORMAT = "mp3" ]; then
|
|
||||||
ffmpeg -i "$wav_file" -vn -codec:a libmp3lame -qscale:a 1 -t "$output_duration" "$out_file"
|
|
||||||
else
|
|
||||||
notify-send "Record Error" "Unknown format $FORMAT"
|
|
||||||
fi
|
|
||||||
rm "$wav_file"
|
|
||||||
|
|
||||||
# Update newest note with recorded audio
|
|
||||||
curl -s localhost:$ankiConnectPort -X POST -d '{
|
|
||||||
|
|
||||||
"action": "updateNoteFields",
|
|
||||||
"version": 6,
|
|
||||||
"params": {
|
|
||||||
"note": {
|
|
||||||
"id": '"$newestNoteId"',
|
|
||||||
"fields": {
|
|
||||||
"'$audioFieldName'": ""
|
|
||||||
},
|
|
||||||
"audio": [{
|
|
||||||
"path": "'"$out_file"'",
|
|
||||||
"filename": "'"$time"'.'$FORMAT'",
|
|
||||||
"fields": [
|
|
||||||
"'$audioFieldName'"
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}'
|
|
||||||
# opens changed note, comment if you don't want it.
|
notify() {
|
||||||
curl -s localhost:$ankiConnectPort -X POST -d '{
|
# Best-effort notification; keep script running if notify-send is missing.
|
||||||
"action": "guiBrowse",
|
if command -v notify-send >/dev/null 2>&1; then
|
||||||
"version": 6,
|
notify-send -t 1000 "$@"
|
||||||
"params": {
|
fi
|
||||||
"query": "nid:'"$newestNoteId"'"
|
}
|
||||||
}
|
|
||||||
}'
|
get_active_sink() {
|
||||||
notify-send -t 1000 "Audio recording copied"
|
pactl list sinks short 2>/dev/null | awk '$6=="RUNNING"{print $1; exit 0}'
|
||||||
rm "$out_file"
|
}
|
||||||
fi
|
|
||||||
|
get_newest_note_id() {
|
||||||
|
local response
|
||||||
|
response=$(curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' \
|
||||||
|
-d '{"action":"findNotes","version":6,"params":{"query":"is:new"}}')
|
||||||
|
jq -r '.result[-1] // empty' <<<"$response"
|
||||||
|
}
|
||||||
|
|
||||||
|
update_anki_note() {
|
||||||
|
local note_id="$1" audio_path="$2" filename="$3"
|
||||||
|
|
||||||
|
local payload
|
||||||
|
payload=$(jq -n --argjson noteId "$note_id" --arg field "$AUDIO_FIELD_NAME" \
|
||||||
|
--arg path "$audio_path" --arg filename "$filename" '
|
||||||
|
{action:"updateNoteFields",version:6,
|
||||||
|
params:{note:{id:$noteId,fields:{($field):""},
|
||||||
|
audio:[{path:$path,filename:$filename,fields:[$field]}]}}}')
|
||||||
|
|
||||||
|
curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' -d "$payload" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
open_note_in_browser() {
|
||||||
|
local note_id="$1"
|
||||||
|
local payload
|
||||||
|
payload=$(jq -n --argjson noteId "$note_id" '
|
||||||
|
{action:"guiBrowse",version:6,params:{query:("nid:" + ($noteId|tostring))}}')
|
||||||
|
curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' -d "$payload" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
record_audio() {
|
||||||
|
local note_id="$1"
|
||||||
|
local sink
|
||||||
|
sink=$(get_active_sink) || true
|
||||||
|
|
||||||
|
if [[ -z "$sink" ]]; then
|
||||||
|
notify "Record Error" "No running PulseAudio sink found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$CACHE_DIR"
|
||||||
|
|
||||||
|
local timestamp wav_file out_file
|
||||||
|
timestamp=$(date +%s)
|
||||||
|
wav_file="$CACHE_DIR/$timestamp.wav"
|
||||||
|
out_file="$CACHE_DIR/$timestamp.$FORMAT"
|
||||||
|
|
||||||
|
notify "Audio recording started"
|
||||||
|
|
||||||
|
if ! timeout "$RECORD_TIMEOUT" parec -d"$sink" --file-format=wav "$wav_file"; then
|
||||||
|
notify "Record Error" "No audio captured (timeout or sink issue)"
|
||||||
|
rm -f "$wav_file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local input_duration output_duration
|
||||||
|
input_duration=$(ffprobe -v error -select_streams a:0 \
|
||||||
|
-show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 "$wav_file")
|
||||||
|
output_duration=$(echo "$input_duration - $CUT_DURATION" | bc -l)
|
||||||
|
|
||||||
|
# Guard against negative durations
|
||||||
|
if [[ $(echo "$output_duration < 0" | bc -l) -eq 1 ]]; then
|
||||||
|
output_duration="0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$FORMAT" in
|
||||||
|
ogg)
|
||||||
|
ffmpeg -nostdin -y -i "$wav_file" -vn -codec:a libvorbis -b:a 64k \
|
||||||
|
-t "$output_duration" "$out_file"
|
||||||
|
;;
|
||||||
|
mp3)
|
||||||
|
ffmpeg -nostdin -y -i "$wav_file" -vn -codec:a libmp3lame -qscale:a 1 \
|
||||||
|
-t "$output_duration" "$out_file"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
notify "Record Error" "Unknown format: $FORMAT"
|
||||||
|
rm -f "$wav_file"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
rm -f "$wav_file"
|
||||||
|
|
||||||
|
update_anki_note "$note_id" "$out_file" "$timestamp.$FORMAT"
|
||||||
|
open_note_in_browser "$note_id"
|
||||||
|
|
||||||
|
notify "Audio recording copied"
|
||||||
|
rm -f "$out_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
for cmd in curl jq ffmpeg ffprobe parec pactl bc; do
|
||||||
|
require_cmd "$cmd"
|
||||||
|
done
|
||||||
|
|
||||||
|
if pgrep -x parec >/dev/null 2>&1; then
|
||||||
|
pkill -x parec
|
||||||
|
notify "Audio recording stopped"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local newest_note
|
||||||
|
newest_note=$(get_newest_note_id)
|
||||||
|
|
||||||
|
if [[ -z "$newest_note" ]]; then
|
||||||
|
notify "Anki Connect" "No new notes found or AnkiConnect unavailable"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
record_audio "$newest_note"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ generate_thumbnail() {
|
|||||||
local temp_thumb="/tmp/rmpv-thumbnail-$$.jpg"
|
local temp_thumb="/tmp/rmpv-thumbnail-$$.jpg"
|
||||||
local thumbnail_file="${video_file%.*}.jpg"
|
local thumbnail_file="${video_file%.*}.jpg"
|
||||||
|
|
||||||
|
# Clean up previous thumbnail
|
||||||
|
rm -f "$THUMBNAIL_PATH"
|
||||||
|
|
||||||
# Validate input
|
# Validate input
|
||||||
if [[ -z "$video_file" ]]; then
|
if [[ -z "$video_file" ]]; then
|
||||||
echo "Error: No video file specified" >&2
|
echo "Error: No video file specified" >&2
|
||||||
@@ -51,28 +54,39 @@ generate_thumbnail() {
|
|||||||
# Generate thumbnail if it doesn't exist
|
# Generate thumbnail if it doesn't exist
|
||||||
if [[ ! -f "$thumbnail_file" ]]; then
|
if [[ ! -f "$thumbnail_file" ]]; then
|
||||||
echo "Generating thumbnail for $(basename "$video_file")..."
|
echo "Generating thumbnail for $(basename "$video_file")..."
|
||||||
if ! ffmpeg -i "$video_file" \
|
# Try generating thumbnail side-by-side
|
||||||
-vf "select='gt(scene,0.4)',scale=320:240:force_original_aspect_ratio=decrease,pad=320:240:(ow-iw)/2:(oh-ih)/2" \
|
if ! ffmpeg -ss 00:00:01 -i "$video_file" \
|
||||||
|
-vf "scale=320:240:force_original_aspect_ratio=decrease,pad=320:240:(ow-iw)/2:(oh-ih)/2" \
|
||||||
-frames:v 1 \
|
-frames:v 1 \
|
||||||
-q:v 4 \
|
-q:v 4 \
|
||||||
"$thumbnail_file" \
|
"$thumbnail_file" \
|
||||||
-loglevel error -y 2>/dev/null; then
|
-loglevel error -y 2>/dev/null; then
|
||||||
echo "Error: Failed to generate thumbnail" >&2
|
|
||||||
return 1
|
# Fallback to temp file if side-by-side fails (e.g. read-only fs)
|
||||||
|
echo "Warning: Failed to write to $thumbnail_file, trying temp location" >&2
|
||||||
|
thumbnail_file="$temp_thumb"
|
||||||
|
|
||||||
|
if ! ffmpeg -ss 00:00:01 -i "$video_file" \
|
||||||
|
-vf "scale=320:240:force_original_aspect_ratio=decrease,pad=320:240:(ow-iw)/2:(oh-ih)/2" \
|
||||||
|
-frames:v 1 \
|
||||||
|
-q:v 4 \
|
||||||
|
"$thumbnail_file" \
|
||||||
|
-loglevel error -y 2>/dev/null; then
|
||||||
|
echo "Error: Failed to generate thumbnail" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Copy to temporary location with error handling
|
# Copy to consistent location for notify-send
|
||||||
if ! cp "$thumbnail_file" "$temp_thumb" 2>/dev/null; then
|
# We use a fixed path so notify-send always finds it
|
||||||
echo "Error: Failed to copy thumbnail to temporary location" >&2
|
if cp "$thumbnail_file" "$THUMBNAIL_PATH" 2>/dev/null; then
|
||||||
return 1
|
echo "Thumbnail ready at: $THUMBNAIL_PATH"
|
||||||
|
ls -l "$THUMBNAIL_PATH"
|
||||||
|
file "$THUMBNAIL_PATH"
|
||||||
|
else
|
||||||
|
echo "Error: Failed to copy thumbnail to $THUMBNAIL_PATH" >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create symlink for consistent access
|
|
||||||
ln -sf "$temp_thumb" /tmp/rmpv-thumbnail.jpg 2>/dev/null
|
|
||||||
sleep 0.1
|
|
||||||
|
|
||||||
echo "Thumbnail ready: $temp_thumb"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
choice="$(find . -iname "*[.mkv|.mp4]" | sort -h | rofi -dmenu -i -theme "$THEME" -theme-str 'listview {columns: 1; lines: 15;} window {width: 88%;}' -p "Choose Video")"
|
choice="$(find . -iname "*[.mkv|.mp4]" | sort -h | rofi -dmenu -i -theme "$THEME" -theme-str 'listview {columns: 1; lines: 15;} window {width: 88%;}' -p "Choose Video")"
|
||||||
@@ -89,3 +103,4 @@ notify-send -i "$THUMBNAIL_PATH" "Playing Video" "$(basename "$choice")"
|
|||||||
$COMMAND "$choice" &
|
$COMMAND "$choice" &
|
||||||
|
|
||||||
# vim: ft=sh
|
# vim: ft=sh
|
||||||
|
|
||||||
|
|||||||
171
projects/scripts/screenshot-anki.sh
Normal file → Executable file
171
projects/scripts/screenshot-anki.sh
Normal file → Executable file
@@ -1,75 +1,112 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Version 1.2
|
# Capture a region with slurp+grim. If AnkiConnect is available, attach the
|
||||||
# click and drag to screenshot dragged portion
|
# JPEG to the newest note; otherwise copy a PNG to the clipboard.
|
||||||
# click on specific window to screenshot window area
|
|
||||||
# dependencies: imagemagick, xclip,curl maybe xdotool (see comment below)
|
|
||||||
# shoutout to https://gist.github.com/Cephian/f849e326e3522be9a4386b60b85f2f23 for the original script,
|
|
||||||
# https://github.com/xythh/ added the ankiConnect functionality
|
|
||||||
# if anki is running the image is added to your latest note as a jpg, if anki is not running it's added to your clipboard as a png
|
|
||||||
time=$(date +%s)
|
|
||||||
tmp_file="$HOME/.cache/$time"
|
|
||||||
ankiConnectPort="8765"
|
|
||||||
pictureField="Picture"
|
|
||||||
quality="90"
|
|
||||||
|
|
||||||
# This gets your notes marked as new and returns the newest one.
|
set -euo pipefail
|
||||||
newestNoteId=$(curl -s localhost:$ankiConnectPort -X POST -d '{"action": "findNotes", "version": 6, "params": { "query": "is:new"}}' | jq '.result[-1]')
|
|
||||||
|
|
||||||
# you can remove these two lines if you don't have software which
|
ANKI_CONNECT_PORT="${ANKI_CONNECT_PORT:-8765}"
|
||||||
# makes your mouse disappear when you use the keyboard (e.g. xbanish, unclutter)
|
PICTURE_FIELD="${PICTURE_FIELD:-Picture}"
|
||||||
# https://github.com/ImageMagick/ImageMagick/issues/1745#issuecomment-777747494
|
QUALITY="${QUALITY:-90}"
|
||||||
xdotool mousemove_relative 1 1
|
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/screenshot-anki"
|
||||||
xdotool mousemove_relative -- -1 -1
|
ANKI_URL="http://localhost:${ANKI_CONNECT_PORT}"
|
||||||
|
REQUIREMENTS=(slurp grim wl-copy xdotool curl jq)
|
||||||
|
|
||||||
# if anki connect is running it will return your latest note id, and the following code will run, if anki connect is not running nothing is return.
|
notify() {
|
||||||
if [ "$newestNoteId" != "" ]; then
|
if command -v notify-send >/dev/null 2>&1; then
|
||||||
if ! import -quality $quality "$tmp_file.jpg"; then
|
notify-send "$@"
|
||||||
# most likley reason this returns a error, is for fullscreen applications that take full control which does not allowing imagemagick to select the area, use windowed fullscreen or if running wine use a virtual desktop to avoid this.
|
fi
|
||||||
notify-send "Error screenshoting " "most likely unable to find selection"
|
}
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
curl -s localhost:$ankiConnectPort -X POST -d '{
|
require_cmd() {
|
||||||
"action": "updateNoteFields",
|
command -v "$1" >/dev/null 2>&1 || {
|
||||||
"version": 6,
|
notify "Missing dependency" "$1 is required"
|
||||||
"params": {
|
exit 1
|
||||||
"note": {
|
|
||||||
"id": '"$newestNoteId"',
|
|
||||||
"fields": {
|
|
||||||
"'$pictureField'": ""
|
|
||||||
},
|
|
||||||
"picture": [{
|
|
||||||
"path": "'"$tmp_file"'.jpg",
|
|
||||||
"filename": "paste-'"$time"'.jpg",
|
|
||||||
"fields": [
|
|
||||||
"'$pictureField'"
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}'
|
}
|
||||||
|
|
||||||
#remove if you don't want anki to show you the card you just edited
|
wiggle_mouse() {
|
||||||
curl -s localhost:$ankiConnectPort -X POST -d '{
|
# Avoid disappearing cursor on some compositors
|
||||||
"action": "guiBrowse",
|
xdotool mousemove_relative 1 1
|
||||||
"version": 6,
|
xdotool mousemove_relative -- -1 -1
|
||||||
"params": {
|
}
|
||||||
"query": "nid:'"$newestNoteId"'"
|
|
||||||
}
|
|
||||||
}'
|
|
||||||
|
|
||||||
#you can comment this if you do not use notifcations.
|
capture_region() {
|
||||||
notify-send "Screenshot Taken" "Added to note"
|
local fmt="$1" quality="$2" output="$3"
|
||||||
rm "$tmp_file.jpg"
|
local geometry
|
||||||
else
|
geometry=$(slurp)
|
||||||
if ! import -quality $quality "$tmp_file.png"; then
|
if [[ -z "$geometry" ]]; then
|
||||||
notify-send "Error screenshoting " "most likely unable to find selection"
|
notify "Screenshot cancelled" "No region selected"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# we use pngs when copying to clipboard because they have greater support when pasting.
|
if [[ "$fmt" == "jpeg" ]]; then
|
||||||
xclip -selection clipboard -target image/png -i "$tmp_file.png"
|
grim -g "$geometry" -t jpeg -q "$quality" "$output"
|
||||||
rm "$tmp_file.png"
|
else
|
||||||
#you can comment this if you do not use notifcations.
|
grim -g "$geometry" -t png "$output"
|
||||||
notify-send "Screenshot Taken" "Copied to clipboard"
|
fi
|
||||||
fi
|
}
|
||||||
|
|
||||||
|
copy_to_clipboard() {
|
||||||
|
local file="$1"
|
||||||
|
if ! wl-copy <"$file"; then
|
||||||
|
notify "Error copying screenshot" "wl-copy failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_newest_note_id() {
|
||||||
|
local response
|
||||||
|
response=$(curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' \
|
||||||
|
-d '{"action":"findNotes","version":6,"params":{"query":"is:new"}}')
|
||||||
|
jq -r '.result[-1] // empty' <<<"$response"
|
||||||
|
}
|
||||||
|
|
||||||
|
update_note_with_image() {
|
||||||
|
local note_id="$1" image_path="$2" filename="$3"
|
||||||
|
local payload
|
||||||
|
payload=$(jq -n --argjson noteId "$note_id" --arg field "$PICTURE_FIELD" \
|
||||||
|
--arg path "$image_path" --arg filename "$filename" '
|
||||||
|
{action:"updateNoteFields",version:6,
|
||||||
|
params:{note:{id:$noteId,fields:{($field):""},
|
||||||
|
picture:[{path:$path,filename:$filename,fields:[$field]}]}}}')
|
||||||
|
curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' -d "$payload" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
open_note_in_browser() {
|
||||||
|
local note_id="$1"
|
||||||
|
local payload
|
||||||
|
payload=$(jq -n --argjson noteId "$note_id" '
|
||||||
|
{action:"guiBrowse",version:6,params:{query:("nid:" + ($noteId|tostring))}}')
|
||||||
|
curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' -d "$payload" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
for cmd in "${REQUIREMENTS[@]}"; do
|
||||||
|
require_cmd "$cmd"
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$CACHE_DIR"
|
||||||
|
local timestamp base newest_note image_path
|
||||||
|
timestamp=$(date +%s)
|
||||||
|
base="$CACHE_DIR/$timestamp"
|
||||||
|
|
||||||
|
wiggle_mouse
|
||||||
|
newest_note=$(get_newest_note_id)
|
||||||
|
|
||||||
|
if [[ -n "$newest_note" ]]; then
|
||||||
|
image_path="$base.jpg"
|
||||||
|
capture_region "jpeg" "$QUALITY" "$image_path"
|
||||||
|
update_note_with_image "$newest_note" "$image_path" "paste-$timestamp.jpg"
|
||||||
|
open_note_in_browser "$newest_note"
|
||||||
|
notify -i "$image_path" "Screenshot Taken" "Added to Anki note"
|
||||||
|
rm -f "$image_path"
|
||||||
|
else
|
||||||
|
image_path="$base.png"
|
||||||
|
capture_region "png" "" "$image_path"
|
||||||
|
copy_to_clipboard "$image_path"
|
||||||
|
notify -i "$image_path" "Screenshot Taken" "Copied to clipboard"
|
||||||
|
rm -f "$image_path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|||||||
Reference in New Issue
Block a user