nix

configuration files that power my machines
Log | Files | Refs | README | LICENSE

commit 3cdba189376aba0d956d679843b4962891dfa0e8
parent 5dfbbc048d64f38be024b8df273c423ea940c1aa
Author: mtmn <miro@haravara.org>
Date:   Sat, 27 Jun 2026 20:13:22 +0200

fix f2aif missing tags, shaarli perms, etc.

Diffstat:
Mhosts/void/overlays/config/magdalena/config.json.nix | 0
Dmodules/mixins/dotfiles/bin/aif2f | 121-------------------------------------------------------------------------------
Mmodules/mixins/dotfiles/bin/f2aif | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mmodules/mixins/dotfiles/config/shell/rc/aliased-short-names | 2+-
Mmodules/mixins/dotfiles/config/shell/rc/command_overrides | 8++------
Mmodules/services/shaarli.nix | 4+++-
6 files changed, 158 insertions(+), 167 deletions(-)

diff --git a/hosts/void/overlays/config/magdalena/config.json.nix b/hosts/void/overlays/config/magdalena/config.json.nix Binary files differ. diff --git a/modules/mixins/dotfiles/bin/aif2f b/modules/mixins/dotfiles/bin/aif2f @@ -1,121 +0,0 @@ -#!/usr/bin/env python3 -import subprocess -import argparse -from pathlib import Path - - -def check_artwork(file_path): - result = subprocess.run( - [ - "ffprobe", - "-v", "error", - "-select_streams", "v", - "-show_entries", "stream=codec_type", - "-of", "default=noprint_wrappers=1:nokey=1", - str(file_path), - ], - capture_output=True, - text=True, - ) - return "video" in result.stdout - - -def convert_aif_to_flac(input_path, delete_original=True): - output_path = input_path.with_suffix(".flac") - - if output_path.exists(): - print(f"Skipping {input_path} - {output_path} already exists") - return True - - print(f"Converting {input_path} to {output_path}") - - try: - subprocess.run( - [ - "ffmpeg", - "-i", str(input_path), - "-c:a", "flac", - "-compression_level", "8", - "-map", "0:a", - "-map", "0:v?", - "-map_metadata", "0", - str(output_path), - ], - check=True, - capture_output=True, - ) - - had_artwork = check_artwork(input_path) - if had_artwork: - if check_artwork(output_path): - print(f"Successfully converted {input_path} (artwork preserved)") - else: - print(f"Warning: Artwork may not have transferred for {input_path}") - else: - print(f"Successfully converted {input_path}") - - if delete_original: - try: - input_path.unlink() - print(f"Deleted original file: {input_path}") - except Exception as e: - print(f"Warning: Could not delete original file {input_path}: {e}") - - return True - - except subprocess.CalledProcessError as e: - print(f"Error converting {input_path}:") - print(f"ffmpeg error: {e.stderr.decode()}") - return False - except Exception as e: - print(f"Unexpected error converting {input_path}: {e}") - return False - - -def main(): - parser = argparse.ArgumentParser( - description="Convert AIF/AIFF files to FLAC format recursively." - ) - parser.add_argument("folder_path", help="Path to the folder containing AIF files") - parser.add_argument( - "--keep-original", - action="store_true", - help="Keep original AIF files after conversion (default: delete)", - ) - args = parser.parse_args() - - start_dir = Path(args.folder_path).resolve() - if not start_dir.exists(): - print(f"Error: The directory '{start_dir}' does not exist.") - return - if not start_dir.is_dir(): - print(f"Error: '{start_dir}' is not a directory.") - return - - aif_files = list(start_dir.rglob("*.aif")) + list(start_dir.rglob("*.aiff")) - - converted = 0 - deleted = 0 - errors = 0 - - for aif_file in aif_files: - try: - success = convert_aif_to_flac(aif_file, not args.keep_original) - if success: - converted += 1 - if not args.keep_original and not aif_file.exists(): - deleted += 1 - except Exception as e: - print(f"Failed to process {aif_file}: {e}") - errors += 1 - - print("\nConversion Complete!") - print(f"Successfully converted: {converted} files") - if not args.keep_original: - print(f"Original files deleted: {deleted} files") - if errors > 0: - print(f"Errors encountered: {errors} files") - - -if __name__ == "__main__": - main() diff --git a/modules/mixins/dotfiles/bin/f2aif b/modules/mixins/dotfiles/bin/f2aif @@ -1,12 +1,26 @@ #!/usr/bin/env python3 -import os -import subprocess + import argparse +import subprocess +import sys +from concurrent.futures import ThreadPoolExecutor, as_completed +from dataclasses import dataclass from pathlib import Path +@dataclass +class ConversionStats: + converted: int = 0 + artwork_preserved: int = 0 + no_artwork: int = 0 + artwork_lost: int = 0 + skipped: int = 0 + errors: int = 0 + deleted: int = 0 + + def check_artwork(file_path): - """Check if a file contains artwork.""" + """Return True if the file contains an embedded artwork (video) stream.""" result = subprocess.run( [ "ffprobe", @@ -27,23 +41,28 @@ def check_artwork(file_path): def convert_flac_to_aiff(input_path, delete_original=True): - """Convert FLAC file to AIFF format.""" - output_path = input_path.with_suffix(".aif") + """Convert a single FLAC file to AIFF. + + Returns (success, status) where status is one of: + "artwork_preserved", "no_artwork", "artwork_lost", "skipped". + On failure returns (False, "error"). + """ + output_path = input_path.with_suffix(".aiff") if output_path.exists(): print(f"Skipping {input_path} - {output_path} already exists") - return True + return True, "skipped" print(f"Converting {input_path} to {output_path}") try: - subprocess.run( + result = subprocess.run( [ "ffmpeg", "-i", str(input_path), "-c:a", - "pcm_s16be", # Use 16-bit PCM encoding + "pcm_s16be", # 16-bit PCM (AIFF default audio codec) "-map", "0:a", # Map audio stream "-map", @@ -51,9 +70,9 @@ def convert_flac_to_aiff(input_path, delete_original=True): "-map_metadata", "0", # Copy all metadata "-write_id3v2", - "1", # Write ID3v2 tags + "1", # Write ID3v2 chunk (required: AIFF muxer defaults to off) "-id3v2_version", - "3", # Use ID3v2.3 for better compatibility + "3", # ID3v2.3 for broad compatibility "-f", "aiff", str(output_path), @@ -62,31 +81,62 @@ def convert_flac_to_aiff(input_path, delete_original=True): capture_output=True, ) + # Surface any non-fatal ffmpeg warnings (e.g. the optional 0:v? map + # producing a "no video stream" notice). + if result.stderr: + for line in result.stderr.decode(errors="replace").splitlines(): + if line.strip().lower().startswith("warning"): + print(f" ffmpeg warning: {line.strip()}") + had_artwork = check_artwork(input_path) if had_artwork: if check_artwork(output_path): print(f"Successfully converted {input_path} (artwork preserved)") + status = "artwork_preserved" else: - print(f"Warning: Artwork may not have transferred for {input_path}") + # Artwork existed in source but did not make it to the output. + # Keep the original so the user can investigate / re-encode. + print( + f"Warning: Artwork did not transfer for {input_path}; " + "keeping original" + ) + return True, "artwork_lost" else: print(f"Successfully converted {input_path}") + status = "no_artwork" if delete_original: try: input_path.unlink() print(f"Deleted original file: {input_path}") - except Exception as e: + except OSError as e: print(f"Warning: Could not delete original file {input_path}: {str(e)}") - return True + return True, status except subprocess.CalledProcessError as e: print(f"Error converting {input_path}:") - print(f"ffmpeg error: {e.stderr.decode()}") - return False + print(f"ffmpeg error: {e.stderr.decode(errors='replace')}") + return False, "error" except Exception as e: print(f"Unexpected error converting {input_path}: {str(e)}") - return False + return False, "error" + + +def iter_flac_files(start_dir): + """Yield FLAC files under start_dir, case-insensitively. + + pathlib's rglob is case-sensitive on Linux, so *.FLAC would be missed. + """ + lower = list(start_dir.rglob("*.flac")) + upper = list(start_dir.rglob("*.FLAC")) + # Deduplicate while preserving a stable order. + seen = set() + for p in lower + upper: + rp = p.resolve() + if rp not in seen: + seen.add(rp) + yield p def main(): @@ -99,38 +149,102 @@ def main(): action="store_true", help="Keep original FLAC files after conversion (default: delete)", ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Show what would be converted without writing or deleting anything", + ) + parser.add_argument( + "--jobs", + "-j", + type=int, + default=1, + help="Number of files to convert in parallel (default: 1)", + ) args = parser.parse_args() start_dir = Path(args.folder_path).resolve() if not start_dir.exists(): print(f"Error: The directory '{start_dir}' does not exist.") - return + return 1 if not start_dir.is_dir(): print(f"Error: '{start_dir}' is not a directory.") - return - - converted = 0 - deleted = 0 - errors = 0 - - for flac_file in start_dir.rglob("*.flac"): - try: - success = convert_flac_to_aiff(flac_file, not args.keep_original) - if success: - converted += 1 - if not args.keep_original and not flac_file.exists(): - deleted += 1 - except Exception as e: - print(f"Failed to process {flac_file}: {str(e)}") - errors += 1 + return 1 + + files = list(iter_flac_files(start_dir)) + if not files: + print(f"No FLAC files found under {start_dir}") + return 0 + + stats = ConversionStats() + delete_original = not args.keep_original + + def handle(file_path): + return convert_flac_to_aiff(file_path, delete_original) + + if args.dry_run: + for f in files: + print(f"[dry-run] Would convert {f} -> {f.with_suffix('.aiff')}") + print(f"\n{len(files)} file(s) would be converted.") + return 0 + + if args.jobs > 1: + with ThreadPoolExecutor(max_workers=args.jobs) as pool: + futures = {pool.submit(handle, f): f for f in files} + for fut in as_completed(futures): + f = futures[fut] + try: + success, status = fut.result() + except Exception as e: + print(f"Failed to process {f}: {str(e)}") + stats.errors += 1 + continue + _tally(stats, success, status, f, delete_original) + else: + for f in files: + try: + success, status = handle(f) + except Exception as e: + print(f"Failed to process {f}: {str(e)}") + stats.errors += 1 + continue + _tally(stats, success, status, f, delete_original) print("\nConversion Complete!") - print(f"Successfully converted: {converted} files") + print(f"Successfully converted: {stats.converted} files") + if stats.artwork_preserved: + print(f" with artwork preserved: {stats.artwork_preserved}") + if stats.no_artwork: + print(f" without artwork: {stats.no_artwork}") + if stats.skipped: + print(f"Skipped (already exist): {stats.skipped} files") + if stats.artwork_lost: + print(f"Artwork transfer failed (originals kept): {stats.artwork_lost} files") if not args.keep_original: - print(f"Original files deleted: {deleted} files") - if errors > 0: - print(f"Errors encountered: {errors} files") + print(f"Original files deleted: {stats.deleted} files") + if stats.errors: + print(f"Errors encountered: {stats.errors} files") + + return 1 if stats.errors else 0 + + +def _tally(stats, success, status, file_path, delete_original): + if not success: + stats.errors += 1 + return + if status == "skipped": + stats.skipped += 1 + return + stats.converted += 1 + if status == "artwork_preserved": + stats.artwork_preserved += 1 + elif status == "no_artwork": + stats.no_artwork += 1 + elif status == "artwork_lost": + stats.artwork_lost += 1 + if delete_original and not file_path.exists() and status != "artwork_lost": + stats.deleted += 1 if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/modules/mixins/dotfiles/config/shell/rc/aliased-short-names b/modules/mixins/dotfiles/config/shell/rc/aliased-short-names @@ -1,6 +1,6 @@ #!/bin/sh -alias pw='LC_ALL=C tr -dc a-zA-Z0-9 < /dev/urandom | head -c 20; echo' +alias pws='LC_ALL=C tr -dc a-zA-Z0-9 < /dev/urandom | head -c 20; echo' alias fg='cdg && magdalena look-file && cd -' alias f='magdalena look-file' diff --git a/modules/mixins/dotfiles/config/shell/rc/command_overrides b/modules/mixins/dotfiles/config/shell/rc/command_overrides @@ -60,18 +60,14 @@ done alias mosh='LC_ALL=en_US.UTF-8 mosh' alias sshrc='LC_ALL=en_US.UTF-8 sshrc' -alias leafpad='mousepad' - # Allow other aliases to be used after the program name alias sudo='sudo ' alias watch='watch ' alias gdb='gdb -q' +alias bazel='bazelisk' alias dmesg='dmesg -T --color=always | less -R +G' -alias mksrcinfo='makepkg --printsrcinfo >| .SRCINFO' +alias pass='plass' -alias ncdu='sudo ncdu -x --exclude /nix --exclude $config_home/misc/mnt /' alias pracomer='pracomer --file /tmp/pracomer.log' - -alias bazel='bazelisk' diff --git a/modules/services/shaarli.nix b/modules/services/shaarli.nix @@ -121,10 +121,12 @@ in { rm -f "$themeStamp" fi - for dir in data pagecache cache tmp; do + for dir in data pagecache tmp; do mkdir -p "${webRoot}/$dir" chmod 0750 "${webRoot}/$dir" done + mkdir -p "${webRoot}/cache" + chmod 0755 "${webRoot}/cache" if [ ! -f "$themeStamp" ] || [ "$(cat "$themeStamp")" != "$themeSrc" ]; then rm -rf "${webRoot}/tpl/2004licious"