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:
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"