Skip to content

Instantly share code, notes, and snippets.

@psiborg
Last active June 5, 2025 03:06
Show Gist options
  • Save psiborg/c4da7a48d67b8ac6846ba0956aa78409 to your computer and use it in GitHub Desktop.
Save psiborg/c4da7a48d67b8ac6846ba0956aa78409 to your computer and use it in GitHub Desktop.
Shell Scripts
#!/usr/bin/env bash
# Usage: ./describe_diagrams.sh /path/to/folder
# Exit on error, treat unset variables as an error
set -euo pipefail
# Ensure folder argument is provided
if [ -z "$1" ]; then
echo "Usage: $0 /path/to/folder"
exit 1
fi
FOLDER="$1"
# Check if ImageMagick's 'convert' is installed
if ! command -v convert &> /dev/null; then
echo "Error: 'convert' (ImageMagick) is required but not installed."
exit 1
fi
# Check if ollama is available
if ! command -v ollama &> /dev/null; then
echo "Error: 'ollama' is required but not installed or not in PATH."
exit 1
fi
# --- Define and create output directories ---
# These will be subdirectories within the main FOLDER
TEXT_OUTPUT_SUBDIR="text"
TEMP_IMAGE_SUBDIR="temp"
TEXT_OUTPUT_DIR="${FOLDER}/${TEXT_OUTPUT_SUBDIR}"
TEMP_IMAGE_DIR="${FOLDER}/${TEMP_IMAGE_SUBDIR}"
echo "Ensuring text output directory exists: $TEXT_OUTPUT_DIR"
mkdir -p "$TEXT_OUTPUT_DIR"
echo "Ensuring temporary image directory exists: $TEMP_IMAGE_DIR"
mkdir -p "$TEMP_IMAGE_DIR"
# --- End of output directory creation ---
echo # Add a newline for better readability
# Loop through image files (case-insensitive) using process substitution and NUL delimiters
while IFS= read -r -d $'\0' IMAGE; do
BASENAME=$(basename "$IMAGE")
NAME="${BASENAME%.*}"
# DIRNAME=$(dirname "$IMAGE")
TXTFILE="${TEXT_OUTPUT_DIR}/${NAME}.txt"
EXT_NO_DOT="${IMAGE##*.}"
EXT_LOWER="${EXT_NO_DOT,,}" # Bash 4+ for lowercase
echo "Processing: $BASENAME"
ANALYZE_IMAGE="$IMAGE" # Default to original image
PATH_TO_FLATTENED_GIF="" # Path for the temporary flattened GIF, if created
# Prepare image for analysis
if [[ "$EXT_LOWER" == "gif" ]]; then
# Flattened GIF will go into the TEMP_IMAGE_DIR
PATH_TO_FLATTENED_GIF="${TEMP_IMAGE_DIR}/${NAME}_flattened.png"
echo "Flattening animated GIF..."
if convert "$IMAGE" -coalesce -layers merge +repage "$PATH_TO_FLATTENED_GIF"; then
ANALYZE_IMAGE="$PATH_TO_FLATTENED_GIF" # ollama should analyze this flattened version
else
echo "Error: Failed to flatten GIF: $IMAGE. Skipping."
rm -f "$PATH_TO_FLATTENED_GIF" # Attempt to clean up partial file on error
continue
fi
fi
# Prompt tailored for system design and technical diagrams
PROMPT="You are a technical writer preparing a 1-paragraph summary for a software architecture diagram to be used in a product or solution catalog. Summarize the purpose of the system, highlight the major components (such as services, databases, APIs, etc.), and explain how they interact at a high level. Use clear and professional language suitable for software engineers, architects, and decision-makers. Do not include implementation details or code — focus on structure and flow."
# Run ollama and write output to txt file
echo "Running ollama analysis..."
# Redirect ollama's stdin from /dev/null
if ollama run llava "$PROMPT" "$ANALYZE_IMAGE" > "$TXTFILE" < /dev/null; then
echo "Saved description."
else
echo "Error: ollama command failed for $ANALYZE_IMAGE. Check $TXTFILE for partial output or errors."
# Optionally, you could 'continue' here if you don't want to attempt cleanup for failed ollama
fi
# Clean up temporary image if used
#if [[ -n "$PATH_TO_FLATTENED_GIF" ]] && [[ -f "$PATH_TO_FLATTENED_GIF" ]]; then
#echo "Removing temporary image: $PATH_TO_FLATTENED_GIF"
#rm -f "$PATH_TO_FLATTENED_GIF"
#fi
echo
done < <(find "$FOLDER" -type f \( -iname "*.jpg" -o -iname "*.png" -o -iname "*.gif" \) -print0)
echo "Processing complete."
#!/usr/bin/env python3
import os
from pathlib import Path
from collections import defaultdict
SUPPORTED_EXTENSIONS = {
".gif": "image",
".jpeg": "image",
".jpg": "image",
".md": "text",
".mp3": "audio",
".mp4": "video",
".ogg": "audio",
".pdf": "iframe",
".png": "image",
".svg": "image",
".txt": "text",
".wav": "audio",
".webm": "video",
".webp": "image"
}
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>File Viewer</title>
<style>
body {{ margin: 0; font-family: sans-serif; font-size: 8pt; display: flex; height: 100vh; }}
nav {{
width: 25%;
background: #f4f4f4;
overflow-y: auto;
border-right: 1px solid #ccc;
padding: 10px;
box-sizing: border-box;
}}
.content-area {{
width: 75%;
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden; /* Important for containing flex children */
}}
main {{
flex-grow: 1; /* Takes up available space not used by resizer/preview */
min-height: 50px; /* Minimum height for main content */
padding: 10px;
overflow: auto;
box-sizing: border-box;
}}
#resizer {{
height: 8px; /* Height of the draggable resizer bar */
background: #ccc;
cursor: ns-resize;
flex-shrink: 0; /* Prevent resizer from shrinking */
}}
#preview-pane {{
/* flex-basis will be set by JS. Initial value can be from here or JS. */
flex-basis: 30%; /* Default height as a percentage */
flex-shrink: 0; /* Prevent shrinking beyond its basis unless forced by JS */
min-height: 20px; /* Absolute minimum height, also used for "collapsed" state */
background: #e9e9e9;
overflow-y: auto;
padding: 10px;
box-sizing: border-box;
}}
main iframe, main video, main audio {{
width: 100%;
height: 100%;
display: block;
}}
main img {{
max-width: 100%;
max-height: 100%;
display: block;
object-fit: contain;
}}
.text-container {{ white-space: pre-wrap; font-family: monospace; }}
#preview-pane .text-container {{ font-size: 0.9em; }}
ul {{ list-style-type: disc; padding-left: 12px; }}
li a {{ display: block; padding: 2px 0; text-decoration: none; color: #333; }}
li a:hover {{ background: #ff0; }}
</style>
<script>
// Configuration for resizable/collapsible pane
const MIN_PREVIEW_PANE_HEIGHT_PX = 20; // Min height for preview pane (pixels), also collapsed height
const MIN_MAIN_PANE_HEIGHT_PX = 50; // Min height for main content area (pixels)
const RESIZER_HEIGHT_PX = 8; // Must match #resizer height in CSS (pixels)
let lastUserSetPreviewFlexBasis = '30%'; // Stores the user's preferred size or default
function showContent(type, src, textContent = "", descriptionContent = "") {{
const mainEl = document.getElementById("main");
const previewPaneEl = document.getElementById("preview-pane");
const resizerEl = document.getElementById("resizer");
// Display main content
if (type === "iframe") {{
mainEl.innerHTML = `<iframe src="${{src}}" frameborder="0"></iframe>`;
}} else if (type === "image") {{
mainEl.innerHTML = `<img src="${{src}}">`;
}} else if (type === "audio") {{
mainEl.innerHTML = `<audio controls src="${{src}}"></audio>`;
}} else if (type === "video") {{
mainEl.innerHTML = `<video controls src="${{src}}"></video>`;
}} else if (type === "text") {{
mainEl.innerHTML = `<div class="text-container">${{textContent}}</div>`;
}} else {{
mainEl.innerHTML = `<h2>Preview for ${{type}} not supported yet.</h2>`;
}}
// Display description in preview pane and manage its state
if (descriptionContent && descriptionContent.trim() !== "") {{
previewPaneEl.innerHTML = `<div class="text-container">${{descriptionContent}}</div>`;
previewPaneEl.style.flexBasis = lastUserSetPreviewFlexBasis; // Restore to user's size or default
previewPaneEl.style.display = ''; // Ensure visible
resizerEl.style.display = ''; // Show resizer
previewPaneEl.style.overflowY = 'auto';
}} else {{
previewPaneEl.innerHTML = "<p>No description available.</p>";
previewPaneEl.style.flexBasis = `${{MIN_PREVIEW_PANE_HEIGHT_PX}}px`; // Collapse
// previewPaneEl.style.display = 'none'; // Alternative: hide completely
resizerEl.style.display = 'none'; // Hide resizer when collapsed
previewPaneEl.style.overflowY = 'hidden'; // No scroll needed for "no description"
}}
}}
document.addEventListener('DOMContentLoaded', () => {{
const resizer = document.getElementById('resizer');
const mainPane = document.getElementById('main');
const previewPane = document.getElementById('preview-pane');
const contentArea = document.querySelector('.content-area');
// Initialize lastUserSetPreviewFlexBasis from CSS or set a default
const initialComputedFlexBasis = window.getComputedStyle(previewPane).flexBasis;
if (initialComputedFlexBasis && initialComputedFlexBasis !== 'auto' && parseFloat(initialComputedFlexBasis) >= MIN_PREVIEW_PANE_HEIGHT_PX) {{
lastUserSetPreviewFlexBasis = initialComputedFlexBasis;
}} else {{
lastUserSetPreviewFlexBasis = '30%'; // Fallback default if CSS is not suitable
}}
// Apply initial flexBasis based on whether there's a default description (usually not on first load)
// This means the initial state will be collapsed if the welcome message has no "description"
// Or, we can explicitly set it for the first load state:
if (previewPane.innerHTML.includes("Description will appear here.")) {{
previewPane.style.flexBasis = `${{MIN_PREVIEW_PANE_HEIGHT_PX}}px`;
resizer.style.display = 'none';
previewPane.style.overflowY = 'hidden';
}} else {{
previewPane.style.flexBasis = lastUserSetPreviewFlexBasis;
}}
let isResizing = false;
let startY_mouse;
resizer.addEventListener('mousedown', (e) => {{
e.preventDefault(); // Prevent text selection during drag
isResizing = true;
startY_mouse = e.clientY;
// Store the starting height of the preview pane in pixels
const currentPreviewPaneHeight = previewPane.offsetHeight;
document.body.style.cursor = 'ns-resize';
mainPane.style.userSelect = 'none';
previewPane.style.userSelect = 'none';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
function handleMouseMove(ev) {{
if (!isResizing) return;
const deltaY = ev.clientY - startY_mouse;
let newPreviewHeight = currentPreviewPaneHeight - deltaY;
const contentAreaHeight = contentArea.offsetHeight;
// Max preview height: content area height - min main pane height - resizer height
const maxPreviewHeight = contentAreaHeight - MIN_MAIN_PANE_HEIGHT_PX - RESIZER_HEIGHT_PX;
if (newPreviewHeight < MIN_PREVIEW_PANE_HEIGHT_PX) {{
newPreviewHeight = MIN_PREVIEW_PANE_HEIGHT_PX;
}}
if (newPreviewHeight > maxPreviewHeight) {{
newPreviewHeight = maxPreviewHeight;
}}
previewPane.style.flexBasis = `${{newPreviewHeight}}px`;
}}
function handleMouseUp() {{
if (!isResizing) return;
isResizing = false;
document.body.style.cursor = '';
mainPane.style.userSelect = '';
previewPane.style.userSelect = '';
// Update lastUserSetPreviewFlexBasis with the new size in pixels
lastUserSetPreviewFlexBasis = previewPane.style.flexBasis;
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
}}
}});
}});
</script>
</head>
<body>
<nav>
{tree}
</nav>
<div class="content-area">
<main id="main">
<h2>Select a file to preview</h2>
</main>
<div id="resizer"></div>
<div id="preview-pane">
<p>Description will appear here.</p>
</div>
</div>
</body>
</html>
"""
def build_tree(paths_with_desc_info):
"""
Builds a tree structure from a list of paths, each with its type, content, and description.
paths_with_desc_info: list of tuples (path_str, file_type_str, main_content_str, description_content_str)
"""
tree = lambda: defaultdict(tree)
root = tree()
for path, file_type, main_content, description_content in paths_with_desc_info:
parts = path.split('/')
current = root
for part in parts[:-1]:
current = current[part]
current[parts[-1]] = {
'_type': file_type,
'_content': main_content, # Content of the file itself (if text)
'_description': description_content # Content of the associated .txt/.md
}
return root
def render_tree(d, prefix=""):
html = "<ul>"
for key, value in sorted(d.items()):
if isinstance(value, dict) and "_type" in value: # It's a file node
file_type = value["_type"]
main_content_for_js = value["_content"]
description_content_for_js = value["_description"]
escaped_main_content = main_content_for_js.replace("\\", "\\\\").replace("`", "\\`").replace("$", "\\$").replace("\r", "\\r").replace("\n", "\\n")
escaped_description_content = description_content_for_js.replace("\\", "\\\\").replace("`", "\\`").replace("$", "\\$").replace("\r", "\\r").replace("\n", "\\n")
file_path_for_js = prefix + key
js_escaped_path = file_path_for_js.replace("\\", "\\\\").replace("'", "\\'")
html += (f'<li><a href="javascript:void(0)" '
f'onclick="showContent(\'{file_type}\', \'{js_escaped_path}\', '
f'`{escaped_main_content}`, `{escaped_description_content}`)">{key}</a></li>')
elif isinstance(value, dict): # It's a directory node
html += f"<li>{key}{render_tree(value, prefix + key + '/')}</li>"
html += "</ul>"
return html
def gather_files_nested(base_dir):
file_info_list = []
for root, dirs, files in os.walk(base_dir):
dirs.sort()
files.sort()
for file in files:
ext = Path(file).suffix.lower()
if ext in SUPPORTED_EXTENSIONS:
abs_path = Path(root) / file
rel_path = abs_path.relative_to(base_dir).as_posix()
file_type = SUPPORTED_EXTENSIONS[ext]
content = ""
if file_type == "text":
try:
with open(abs_path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
except Exception:
content = f"Could not read file: {rel_path}"
file_info_list.append((rel_path, file_type, content))
return file_info_list
def create_static_index(directory="."):
output_file = Path(directory) / "index.html"
if output_file.exists():
response = input(f"{output_file} already exists. Overwrite? (y/N): ").strip().lower()
if response != "y":
print("Aborted.")
return
all_files_data = gather_files_nested(directory)
text_file_contents_map = {}
for rel_path, file_type, content in all_files_data:
if file_type == "text":
text_file_contents_map[rel_path] = content
files_data_with_descriptions_attached = []
text_files_used_for_non_text_description = set()
for rel_path_main_file, file_type_main_file, main_content_if_text in all_files_data:
description_text_for_main_file = ""
current_file_p_obj = Path(rel_path_main_file)
potential_desc_txt_p_obj = current_file_p_obj.with_suffix('.txt')
if potential_desc_txt_p_obj != current_file_p_obj:
desc_txt_rel_path_str = potential_desc_txt_p_obj.as_posix()
if desc_txt_rel_path_str in text_file_contents_map:
description_text_for_main_file = text_file_contents_map[desc_txt_rel_path_str]
if file_type_main_file != "text":
text_files_used_for_non_text_description.add(desc_txt_rel_path_str)
if not description_text_for_main_file:
potential_desc_md_p_obj = current_file_p_obj.with_suffix('.md')
if potential_desc_md_p_obj != current_file_p_obj:
desc_md_rel_path_str = potential_desc_md_p_obj.as_posix()
if desc_md_rel_path_str in text_file_contents_map:
description_text_for_main_file = text_file_contents_map[desc_md_rel_path_str]
if file_type_main_file != "text":
text_files_used_for_non_text_description.add(desc_md_rel_path_str)
files_data_with_descriptions_attached.append(
(rel_path_main_file, file_type_main_file, main_content_if_text, description_text_for_main_file)
)
processed_file_data_for_tree = []
for rel_path, file_type, main_content, description_content in files_data_with_descriptions_attached:
if file_type == "text" and rel_path in text_files_used_for_non_text_description:
pass
else:
processed_file_data_for_tree.append(
(rel_path, file_type, main_content, description_content)
)
tree_dict = build_tree(processed_file_data_for_tree)
html_tree = render_tree(tree_dict)
html_output = HTML_TEMPLATE.format(tree=html_tree)
with open(output_file, "w", encoding="utf-8") as f:
f.write(html_output)
print(f"Created index.html in: {output_file.resolve()}")
if __name__ == "__main__":
create_static_index()
#!/usr/bin/env bash
for file in *.mp3 *.flac; do
if [ -f "$file" ]; then
echo "Metadata for: $file"
#ffprobe -i "$file" -show_entries format_tags -v quiet -print_format json
ffprobe -i "$file" -show_entries format_tags -v quiet -print_format flat
echo "-------------------------------------------------------------------------------"
fi
done
#!/usr/bin/env bash
for f in *.flac; do
ffmpeg -i "$f" -ab 320k -map_metadata 0 -id3v2_version 3 "${f%".flac"}.mp3"
done
#!/usr/bin/env bash
for f in *.mkv; do
ffmpeg -i "$f" -x265-params crf=23 "${f%".mkv"}.mp4"
done;
#!/usr/bin/env bash
for f in *.mkv; do
ffmpeg -i "$f" -map 0:v:0 -map 0:a:0 -metadata title="${f%}" -vf "scale=1920:1080" -pix_fmt yuv420p -preset slow -crf 23 "${f%".mkv"}.mp4"
done;
#!/usr/bin/env bash
for f in *.mkv; do
ffmpeg -i "$f" -map 0:v:0 -map 0:a:0 -metadata title="${f%}" -vf "scale=1280:720" -pix_fmt yuv420p -preset slow -crf 23 "${f%".mkv"}.mp4"
done;
#!/usr/bin/env bash
for f in *.mkv; do
ffmpeg -i "$f" "${f%".mkv"}.srt"
ffmpeg -i "$f" -map 0:v:0 -map 0:a:0 -metadata title="${f%}" -codec copy "${f%".mkv"}.mp4"
done;
#!/usr/bin/env bash
for f in *.mkv; do
ffmpeg -i "$f" "${f%".mkv"}.srt"
done;
#!/usr/bin/env bash
for f in *.mp4; do
base="${f%.mp4}"
ffmpeg -i "$f" -vf scale=1280:720 -c:v libx264 -crf 23 -preset medium -c:a copy "${base}_720p_h264.mp4"
done
#!/usr/bin/env bash
ollama --version
echo
ollama list
echo
echo "Getting list of installed models..."
echo
models=$(ollama list | awk 'NR>1 {print $1}')
if [ -z "$models" ]; then
echo "No models found to update."
echo
exit 0
fi
for model in $models; do
echo "Pulling latest for $model..."
echo
ollama pull "$model"
echo
done
ollama list
#!/usr/bin/env bash
yt-dlp -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best' --batch-file $1
#yt-dlp -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best' --batch-file $1 --cookies "/path/to/cookies.txt"
#!/usr/bin/env bash
yt-dlp --extract-audio --audio-format mp3 --audio-quality 0 -o "%(title)s.%(ext)s" $1
#!/usr/bin/env bash
yt-dlp -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best' $1
#yt-dlp -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best' $1 --cookies "/path/to/cookies.txt"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment