This commit is contained in:
2026-03-27 17:49:22 +01:00
parent 88b3ff784a
commit a4ae2287b5
66 changed files with 0 additions and 2264 deletions

View File

@@ -1,57 +0,0 @@
# https://github.com/NixOS/nixpkgs/issues/126590#issuecomment-3194531220
{
config,
pkgs,
lib,
...
}: {
nixpkgs.overlays = lib.singleton (final: prev: {
kdePackages =
prev.kdePackages
// {
plasma-workspace = let
# the package we want to override
basePkg = prev.kdePackages.plasma-workspace;
# a helper package that merges all the XDG_DATA_DIRS into a single directory
xdgdataPkg = pkgs.stdenv.mkDerivation {
name = "${basePkg.name}-xdgdata";
buildInputs = [basePkg];
dontUnpack = true;
dontFixup = true;
dontWrapQtApps = true;
installPhase = ''
mkdir -p $out/share
( IFS=:
for DIR in $XDG_DATA_DIRS; do
if [[ -d "$DIR" ]]; then
cp -r $DIR/. $out/share/
chmod -R u+w $out/share
fi
done
)
'';
};
# undo the XDG_DATA_DIRS injection that is usually done in the qt wrapper
# script and instead inject the path of the above helper package
derivedPkg = basePkg.overrideAttrs {
preFixup = ''
for index in "''${!qtWrapperArgs[@]}"; do
if [[ ''${qtWrapperArgs[$((index+0))]} == "--prefix" ]] && [[ ''${qtWrapperArgs[$((index+1))]} == "XDG_DATA_DIRS" ]]; then
unset -v "qtWrapperArgs[$((index+0))]"
unset -v "qtWrapperArgs[$((index+1))]"
unset -v "qtWrapperArgs[$((index+2))]"
unset -v "qtWrapperArgs[$((index+3))]"
fi
done
qtWrapperArgs=("''${qtWrapperArgs[@]}")
qtWrapperArgs+=(--prefix XDG_DATA_DIRS : "${xdgdataPkg}/share")
qtWrapperArgs+=(--prefix XDG_DATA_DIRS : "$out/share")
'';
};
in
derivedPkg;
};
});
}

View File

@@ -1,51 +0,0 @@
{lib, ...}: {
# Home Manager needs a bit of information about you and the paths it should
# manage.
home.username = "admin-jroeger";
home.homeDirectory = "/home/admin-jroeger";
# hive modules
hive.kitty.enable = true;
hive.ranger.enable = true;
hive.yubikey.enable = true;
hive.yubikey.pinentry = "gnome3";
hive.nix-scripts.enable = true;
hive.zsh.enable = true;
hive.doom.enable = true;
hive.doom.asDefaultEditor = true;
hive.doom.enableCopilot = true;
hive.doom.withNixPkgs = true;
hive.doom.withShellPkgs = true;
hive.doom.withCXXPkgs = true;
hive.doom.withPythonPkgs = true;
hive.jj.enable = true;
# This value determines the Home Manager release that your configuration is
# compatible with. This helps avoid breakage when a new Home Manager release
# introduces backwards incompatible changes.
#
# You should not change this value, even if you update Home Manager. If you do
# want to update the value, then make sure to first check the Home Manager
# release notes.
home.stateVersion = "24.11"; # Please read the comment before changing.
home.sessionVariables = {
EDITOR = lib.mkDefault "vim";
};
# Let Home Manager install and manage itself.
programs.home-manager.enable = true;
# Git
programs.difftastic.enable = true;
programs.difftastic.git.enable = true;
programs.git = {
enable = true;
settings.user.name = "Jonas Röger";
settings.user.email = "jonas.kieran.roeger@iml.fraunhofer.de";
signing = {
signByDefault = true;
key = "4000EB35E1AE0F07";
};
};
}

View File

@@ -1,105 +0,0 @@
{
config,
lib,
...
}: {
imports = [
(./. + "/jonas@comfy-station/" + /borg.nix)
];
# Home Manager needs a bit of information about you and the paths it should
# manage.
home.username = "jonas";
home.homeDirectory = "/home/jonas";
sops = {
age.keyFile = "${config.home.homeDirectory}/.config/sops/age/keys.txt";
};
# hive modules
hive.themes.layan.enable = true;
hive.themes.layan.pkgsInHome = true;
hive.hyprland.enable = true;
hive.swaync.enable = true;
hive.waybar.enable = true;
hive.wlogout.enable = true;
hive.wofi.enable = true;
hive.kitty.enable = true;
hive.nextcloud.enable = true;
hive.firefox = {
enable = true;
plasmaIntegration = true;
passFF = true;
};
hive.kdeconnect.enable = true;
hive.ranger.enable = true;
hive.ssh = {
enable = true;
sopsFile = ../secrets/jonas/ssh.yaml;
keys = ["borg" "passgit"];
};
hive.yubikey.enable = true;
hive.yubikey.withCCID = false;
hive.zsh.enable = true;
hive.nix-scripts.enable = true;
hive.doom.enable = true;
hive.doom.asDefaultEditor = true;
hive.doom.enableCopilot = true;
hive.doom.withNixPkgs = true;
hive.doom.withShellPkgs = true;
hive.doom.withPythonPkgs = true;
hive.jj.enable = true;
# Make session variables available in systemd units
# SEE: https://github.com/nix-community/home-manager/pull/5543
# systemd.user.settings.Manager.DefaultEnvironment =
# lib.mapAttrs (_: lib.mkDefault) config.home.sessionVariables;
xdg.mimeApps = {
enable = true;
defaultApplications = {
"text/html" = "firefox.desktop";
"x-scheme-handler/http" = "firefox.desktop";
"x-scheme-handler/https" = "firefox.desktop";
"x-scheme-handler/about" = "firefox.desktop";
"x-scheme-handler/unknown" = "firefox.desktop";
};
};
xdg.userDirs.enable = true;
xdg.userDirs.createDirectories = true;
xdg.userDirs.extraConfig = {
XDG_WORKSPACES_DIR = "${config.home.homeDirectory}/Workspaces";
XDG_NEXTCLOUD_DIR = "${config.home.homeDirectory}/Nextcloud";
XDG_NOTES_DIR = "${config.home.homeDirectory}/Notes";
};
# This value determines the Home Manager release that your configuration is
# compatible with. This helps avoid breakage when a new Home Manager release
# introduces backwards incompatible changes.
#
# You should not change this value, even if you update Home Manager. If you do
# want to update the value, then make sure to first check the Home Manager
# release notes.
home.stateVersion = "24.11"; # Please read the comment before changing.
home.sessionVariables = {
EDITOR = lib.mkDefault "vim";
};
# Let Home Manager install and manage itself.
programs.home-manager.enable = true;
# Git
programs.difftastic.enable = true;
programs.difftastic.git.enable = true;
programs.git = {
enable = true;
settings.user.name = "Jonas Röger";
settings.user.email = "jonas.roeger@tu-dortmund.de";
signing = {
signByDefault = true;
key = "4000EB35E1AE0F07";
};
};
}

View File

@@ -1,131 +0,0 @@
{config, ...}: let
defaultChecks = [
{
name = "repository";
frequency = "2 weeks";
}
{
name = "archives";
frequency = "4 weeks";
}
{
name = "data";
frequency = "6 weeks";
}
{
name = "extract";
frequency = "6 weeks";
}
];
passwordFile = "${config.home.homeDirectory}/.config/borg/password";
encCmd = ''cat ${passwordFile}'';
repo = "ssh://borg.jroeger.de/./comfy-station";
in {
sops.secrets = {
"borg/password" = {
sopsFile = ../../secrets/jonas/borg.yaml;
key = "password";
path = passwordFile;
};
};
services.borgmatic.enable = true;
services.borgmatic.frequency = "hourly";
programs.borgmatic.enable = true;
programs.borgmatic.backups = {
workspaces = {
location = {
sourceDirectories = ["${config.xdg.userDirs.extraConfig.XDG_WORKSPACES_DIR}"];
repositories = [repo];
excludeHomeManagerSymlinks = true;
extraConfig = {
archive_name_format = "{hostname}-workspaces-{now}";
exclude_patterns = [
"*/.venv"
"__pycache__"
];
};
};
retention = {
keepDaily = 7;
keepHourly = 12;
keepWeekly = 4;
keepMonthly = 6;
};
storage = {
encryptionPasscommand = encCmd;
};
consistency.checks = defaultChecks;
};
media = {
location = {
sourceDirectories = [
"${config.xdg.userDirs.documents}"
"${config.xdg.userDirs.music}"
"${config.home.homeDirectory}/org"
"${config.home.homeDirectory}/Obsidian"
"${config.home.homeDirectory}/Zotero"
];
repositories = [repo];
excludeHomeManagerSymlinks = true;
extraConfig = {
archive_name_format = "{hostname}-media-{now}";
};
};
retention = {
keepDaily = 7;
keepWeekly = 2;
keepMonthly = 6;
};
storage = {
encryptionPasscommand = encCmd;
};
consistency.checks = defaultChecks;
};
sec = {
location = {
sourceDirectories = [
"${config.xdg.configHome}/sops"
"${config.home.homeDirectory}/Stuff/sec"
"${config.home.homeDirectory}/.password-store"
];
repositories = [repo];
excludeHomeManagerSymlinks = true;
extraConfig = {
archive_name_format = "{hostname}-sec-{now}";
};
};
retention = {
keepDaily = 7;
keepWeekly = 2;
keepMonthly = 6;
};
storage = {
encryptionPasscommand = encCmd;
};
consistency.checks = defaultChecks;
};
var = {
location = {
sourceDirectories = [
"${config.xdg.userDirs.desktop}"
];
repositories = [repo];
excludeHomeManagerSymlinks = true;
extraConfig = {
archive_name_format = "{hostname}-var-{now}";
};
};
retention = {
keepDaily = 7;
keepWeekly = 2;
keepMonthly = 6;
};
storage = {
encryptionPasscommand = encCmd;
};
consistency.checks = defaultChecks;
};
};
}

View File

@@ -1,44 +0,0 @@
{lib, ...}: {
# Home Manager needs a bit of information about you and the paths it should
# manage.
home.username = "jonas";
home.homeDirectory = "/home/jonas";
# hive modules
hive.zsh.enable = true;
hive.nix-scripts.enable = true;
hive.ranger.enable = true;
hive.doom.enable = true;
hive.doom.asDefaultEditor = true;
hive.doom.withNixPkgs = true;
hive.doom.withShellPkgs = true;
# This value determines the Home Manager release that your configuration is
# compatible with. This helps avoid breakage when a new Home Manager release
# introduces backwards incompatible changes.
#
# You should not change this value, even if you update Home Manager. If you do
# want to update the value, then make sure to first check the Home Manager
# release notes.
home.stateVersion = "24.11"; # Please read the comment before changing.
home.sessionVariables = {
EDITOR = lib.mkDefault "vim";
};
# Let Home Manager install and manage itself.
programs.home-manager.enable = true;
# Git
programs.difftastic.enable = true;
programs.difftastic.git.enable = true;
programs.git = {
enable = true;
settings.user.name = "Jonas Röger";
settings.user.email = "jonas.roeger@tu-dortmund.de";
signing = {
signByDefault = true;
key = "4000EB35E1AE0F07";
};
};
}

View File

@@ -1,105 +0,0 @@
{
config,
lib,
...
}: rec {
# Home Manager needs a bit of information about you and the paths it should
# manage.
home.username = "jonas";
home.homeDirectory = "/home/jonas";
sops = {
age.keyFile = "${home.homeDirectory}/.config/sops/age/keys.txt";
};
sops.secrets.gotifyDaemonToken = {
sopsFile = ../secrets/jonas/gotify.yaml;
key = "monolithDesktopToken";
};
sops.secrets.gotifyCLIToken = {
sopsFile = ../secrets/jonas/gotify.yaml;
key = "cliToken";
};
# hive moduless
hive.doom.enable = true;
hive.doom.asDefaultEditor = true;
hive.doom.enableCopilot = false;
hive.doom.enableTidal = false;
hive.doom.withNixPkgs = true;
hive.doom.withShellPkgs = true;
hive.doom.withPythonPkgs = true;
hive.firefox = {
enable = true;
plasmaIntegration = false;
passFF = true;
};
hive.kdeconnect.enable = false;
hive.kdeconnect.indicatorOnly = false;
hive.nextcloud.enable = false; # kwallet bug
hive.nix-scripts.enable = true;
hive.ranger.enable = true;
hive.themes.layan.enable = true;
hive.yubikey.enable = true;
hive.zsh.enable = true;
hive.jj.enable = true;
hive.gotify = {
cli.enable = true;
daemon.enable = true;
cli.tokenSopsKey = config.sops.secrets.gotifyCLIToken.name;
daemon.tokenSopsKey = config.sops.secrets.gotifyDaemonToken.name;
host = "gotify.jroeger.de";
};
# Make session variables available in systemd units
# SEE: https://github.com/nix-community/home-manager/pull/5543
# systemd.user.settings.Manager.DefaultEnvironment =
# lib.mapAttrs (_: lib.mkDefault) config.home.sessionVariables;
xdg.mimeApps = {
enable = true;
defaultApplications = {
"text/html" = "firefox.desktop";
"x-scheme-handler/http" = "firefox.desktop";
"x-scheme-handler/https" = "firefox.desktop";
"x-scheme-handler/about" = "firefox.desktop";
"x-scheme-handler/unknown" = "firefox.desktop";
};
};
xdg.userDirs.enable = true;
xdg.userDirs.createDirectories = true;
xdg.userDirs.extraConfig = {
XDG_WORKSPACES_DIR = "${config.home.homeDirectory}/Workspaces";
XDG_NEXTCLOUD_DIR = "${config.home.homeDirectory}/Nextcloud";
XDG_NOTES_DIR = "${config.home.homeDirectory}/Notes";
};
# This value determines the Home Manager release that your configuration is
# compatible with. This helps avoid breakage when a new Home Manager release
# introduces backwards incompatible changes.
#
# You should not change this value, even if you update Home Manager. If you do
# want to update the value, then make sure to first check the Home Manager
# release notes.
home.stateVersion = "24.11"; # Please read the comment before changing.
home.sessionVariables = {
EDITOR = lib.mkDefault "vim";
};
# Let Home Manager install and manage itself.
programs.home-manager.enable = true;
# Git
programs.difftastic.enable = true;
programs.difftastic.git.enable = true;
programs.git = {
enable = true;
settings.user.name = "Jonas Röger";
settings.user.email = "jonas.roeger@tu-dortmund.de";
signing = {
signByDefault = true;
key = "4000EB35E1AE0F07";
};
};
}

View File

@@ -1,8 +0,0 @@
{inputs, ...}: {
flake.overlays.unstable = final: prev: {
unstable = import inputs.nixpkgs-unstable {
system = prev.system;
config.allowUnfree = true;
};
};
}

View File

@@ -1 +0,0 @@
use flake ../../#bulk-transcode --show-trace

View File

@@ -1,96 +0,0 @@
#!/usr/bin/env bash
declare -rA presets=(
[davinci-resolve]="-c:v dnxhd -profile:v dnxhr_hq -pix_fmt yuv422p -c:a pcm_s16le"
[instagram]="-vf scale='if(gte(iw/ih,1),1920,-1)':'if(gte(iw/ih,1),-1,1920)':flags=lanczos -r 30 -c:v libx264 -profile:v high -level 4.1 -pix_fmt yuv420p -preset slow -crf 18 -bf 2 -g 15 -keyint_min 15 -x264-params \"open-gop=0:cabac=1:b-pyramid=none\" -movflags +faststart -c:a aac -b:a 96k"
[insta-4k]="-r 30 -c:v libx264 -profile:v high -level 4.1 -pix_fmt yuv420p -preset slow -crf 18 -bf 2 -g 15 -keyint_min 15 -x264-params \"open-gop=0:cabac=1:b-pyramid=none\" -movflags +faststart -c:a aac -b:a 96k"
[storage-hevc]="-c:v libx265 -preset slower -crf 18 -pix_fmt yuv420p10le -x265-params aq-mode=3:aq-strength=1.0:psy-rd=1.8:psy-rdoq=1.0 -c:a copy"
[storage-av1]="-c:v libsvtav1 -preset 6 -crf 28 -pix_fmt yuv420p -g 240 -svtav1-params tune=0:aq-mode=2 -c:a copy"
[storage-av1-1080p]="-vf scale='if(gte(iw/ih,1),1920,-1)':'if(gte(iw/ih,1),-1,1920)' -c:v libsvtav1 -preset 6 -crf 28 -pix_fmt yuv420p -g 240 -svtav1-params tune=0:aq-mode=2 -c:a copy"
[storage-av1-nvenc]="-c:v av1_nvenc -cq 28 -preset slow -pix_fmt yuv420p10le -c:a copy"
[network]="-c:v libx264 -preset slow -crf 22 -pix_fmt yuv420p -c:a aac -b:a 128k"
[network-1080p]="-vf scale='if(gte(iw/ih,1),1920,-1)':'if(gte(iw/ih,1),-1,1920)' -c:v libx264 -preset slow -crf 22 -pix_fmt yuv420p -c:a aac -b:a 128k"
[whatsapp]="-vf scale='if(gte(iw/ih,1),1920,-1)':'if(gte(iw/ih,1),-1,1920)' -c:v libx264 -preset slow -crf 30 -profile:v baseline -level 3.0 -pix_fmt yuv420p -r 25 -g 50 -c:a aac -b:a 160k -r:a 44100"
)
declare -rA containers=(
[davinci-resolve]="mov"
[instagram]="mp4"
[insta-4k]="mp4"
[storage-hevc]="mkv"
[storage-av1]="mkv"
[storage-av1-1080p]="mkv"
[storage-av1-nvenc]="mkv"
[network]="mp4"
[network-1080p]="mp4"
[whatsapp]="mp4"
)
where="${1:-.}"
dest="${2:-$where}"
selection=$(find "$where" -type f | fzf --multi --preview 'ffprobe -v error -show_format -show_streams {}' --preview-window=up:wrap)
preset=$(
printf '%s\n' "${!presets[@]}" | \
fzf --multi --prompt "Select a preset"
)
flags="${presets[$preset]}"
container="${containers[$preset]}"
output_dir=$(find "$dest" -type d ! -name '.*' ! -path '*/.*/*' | fzf --preview 'tree -C {}' --preview-window=up:wrap --prompt "Select output directory: ")
if gum confirm "Flatten the directory structure?";
then
flatten=true
else
flatten=false
fi
function transcode_job {
local ifile="$1"
local output_dir="$2"
local flatten="$3"
local where="$4"
local flags="$5"
local container="$6"
local fname=$(basename "$ifile")
local segment=$(realpath --relative-to="$where" "$ifile")
if [ "$flatten" = true ]; then
output_file="$output_dir/$fname.$container"
else
output_file="$output_dir/$segment.$container"
fi
tmp_file=$(mktemp)
echo "Running Command: ffmpeg -y -i $ifile $flags $output_file" >> "$tmp_file"
mkdir -p "$(dirname "$output_file")"
if ffmpeg -y -i "$ifile" $(echo -n "$flags") "$output_file" 2>> "$tmp_file";
then
rm -f "$tmp_file"
else
# gum log "Failed to transcode $ifile. Check ./error.log for details."
cat "$tmp_file" >> error.log
rm -f "$tmp_file"
fi
}
export -f transcode_job
mapfile -t files <<< "$selection"
len=${#files[@]}
i=1
for file in "${files[@]}"; do
if [[ -f "$file" ]]; then
gum spin --spinner dot --title "[$i/$len] Transcoding $file" -- bash -c "source <(declare -f transcode_job); transcode_job \"$file\" \"$output_dir\" \"$flatten\" \"$where\" \"$flags\" \"$container\""
else
echo "Skipping invalid file: $file" >&2
fi
((i++))
done

View File

@@ -1,9 +0,0 @@
{
flake.overlays.bulk-transcode = final: prev: {
bulk-transcode = import ./overlay.nix {inherit final prev;};
};
perSystem = {pkgs, ...}: {
packages.bulk-transcode = pkgs.callPackage ./derivation.nix {};
devShells.bulk-transcode = import ./shell.nix {inherit pkgs;};
};
}

View File

@@ -1,35 +0,0 @@
{
bash,
coreutils,
ffmpeg,
findutils,
fzf,
gum,
lib,
makeWrapper,
stdenv,
tree,
...
}:
stdenv.mkDerivation (finalAttrs: {
name = "bulk-transcode";
src = ./.;
buildInputs = [
bash
coreutils
ffmpeg
findutils
fzf
gum
makeWrapper
tree
];
installPhase = ''
install -Dm755 bulk-transcode.sh $out/bin/bulk-transcode
wrapProgram $out/bin/bulk-transcode \
--set PATH "${lib.makeBinPath finalAttrs.buildInputs}"
'';
})

View File

@@ -1,7 +0,0 @@
{pkgs ? import <nixpkgs> {}}: let
bin = pkgs.callPackage ./derivation.nix {};
in
pkgs.mkShell {
name = "bulk-transcode";
inputsFrom = [bin];
}

View File

@@ -1,40 +0,0 @@
{
appimageTools,
fetchurl,
makeWrapper,
...
}: let
pname = "crossover";
version = "3.1.5";
src = fetchurl {
url = "https://github.com/lacymorrow/crossover/releases/download/v${version}/CrossOver-${version}-x86_64.AppImage";
sha256 = "sha256-64RPal8n1PJh1LB+CTyNFt04Pw1lVgcsyc63S8yQ/DA=";
};
appimageContents = appimageTools.extract {
inherit pname version src;
};
in
appimageTools.wrapType2 {
inherit pname version src;
nativeBuildInputs = [makeWrapper];
extraInstallCommands = ''
wrapProgram $out/bin/${pname} --add-flags "--no-sandbox"
# Create a minimal .desktop file manually
mkdir -p $out/share/applications
cat > $out/share/applications/${pname}.desktop <<EOF
[Desktop Entry]
Name=${pname}
Exec=${pname} %U
Icon=${pname}
Type=Application
Categories=Utility;
EOF
# Optionally extract icon from AppImage (if available)
# You can also manually install an icon here:
mkdir -p $out/share/icons/hicolor/0x0/apps
cp ${appimageContents}/usr/share/icons/hicolor/0x0/apps/${pname}.png $out/share/icons/hicolor/0x0/apps/${pname}.png || true
'';
}

View File

@@ -1,8 +0,0 @@
final: _: {
hive = {
crossover = final.callPackage ./crossover.nix {};
bulk-transcode = final.callPackage ./bulk-transcode {};
spotify-shortcuts = final.callPackage ./spotify-shortcuts/derivation.nix {};
layan-qt6 = final.kdePackages.callPackage ./layan-qt6.nix {};
};
}

View File

@@ -1,52 +0,0 @@
{
stdenv,
lib,
fetchFromGitHub,
gitUpdater,
kdeclarative,
libplasma,
plasma-workspace,
}:
stdenv.mkDerivation {
pname = "layan-kde-qt6";
version = "2025-02-13";
src = fetchFromGitHub {
owner = "vinceliuice";
repo = "Layan-kde";
rev = "ace0b1d93e5f08c650630c146b2d637e1e2e6cd1";
hash = "sha256-T69bGjfZeOsJLmOZKps9N2wMv5VKYeo1ipGEsLAS+Sg=";
};
# Propagate sddm theme dependencies to user env otherwise sddm does
# not find them. Putting them in buildInputs is not enough.
propagatedUserEnvPkgs = [
kdeclarative
libplasma
plasma-workspace
];
postPatch = ''
patchShebangs install.sh
substituteInPlace install.sh \
--replace '$HOME/.local' $out \
--replace '$HOME/.config' $out/share
'';
installPhase = ''
runHook preInstall
name= ./install.sh --dest $out/share/themes
runHook postInstall
'';
passthru.updateScript = gitUpdater {};
meta = with lib; {
description = "Flat Design theme for KDE Plasma desktop";
homepage = "https://github.com/vinceliuice/Layan-kde";
license = licenses.gpl3Only;
platforms = platforms.all;
};
}

View File

@@ -1 +0,0 @@
use flake ../../#spotify-shortcuts --show-trace

View File

@@ -1,2 +0,0 @@
{pkgs ? import <nixpkgs> {}}:
pkgs.callPackage ./derivation.nix {}

View File

@@ -1,9 +0,0 @@
{python3Packages}:
with python3Packages;
buildPythonApplication {
name = "spotify-shortcuts";
propagatedBuildInputs = [spotipy pyxdg desktop-notifier];
pyproject = true;
build-system = [setuptools];
src = ./.;
}

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env python
from setuptools import setup, find_packages
setup(
name="spotify_shortcuts",
version="1.0",
packages=find_packages(),
entry_points={
"console_scripts": [
"spotisc=spotify_shortcuts.run:main",
"spotify-like=spotify_shortcuts.spotify_like:main",
"spotify-pl-add=spotify_shortcuts.spotify_pl_add:main",
],
},
)

View File

@@ -1,15 +0,0 @@
{pkgs ? import <nixpkgs> {}}: let
drv = pkgs.callPackage ./derivation.nix {};
in
pkgs.mkShell {
packages = [
pkgs.pyright
pkgs.black
];
inputsFrom = [drv];
shellHook = ''
export PYTHONPATH="$PYTHONPATH:$(pwd)"
'';
}

View File

@@ -1,4 +0,0 @@
from spotify_shortcuts.run import main
if __name__ == "__main__":
main()

View File

@@ -1,74 +0,0 @@
from pathlib import Path
from dataclasses import dataclass
from argparse import ArgumentParser
from typing import List
from os import getenv
from sys import argv
from xdg.BaseDirectory import xdg_cache_home
from json import loads
@dataclass
class Config:
cache_file: Path = Path(xdg_cache_home) / Path("spotify-shortcuts.json")
client_id: str | None = None
client_secret: str | None = None
notifications: bool = True
@staticmethod
def from_file(path: Path):
if not path.exists():
raise FileNotFoundError(f"Configuration file {path} does not exist.")
with open(path, "r") as f:
data = loads(f.read())
return Config(
cache_file=Path(data.get("cacheFile", Config.cache_file)),
client_id=data.get("clientId", Config.client_id),
client_secret=data.get("clientSecret", Config.client_secret),
)
def load_config(args: List[str] = argv[1:]) -> Config:
parser = ArgumentParser(description="Spotify CLI Tool")
parser.add_argument(
"--cache-file",
type=Path,
default=Config.cache_file,
help="Path to the cache file for Spotify authentication",
)
parser.add_argument(
"--client-id",
type=str,
default=Config.client_id,
help="Spotify API Client ID",
)
parser.add_argument(
"--client-secret",
type=str,
default=Config.client_secret,
help="Spotify API Client Secret",
)
parser.add_argument(
"--config-file",
type=str,
help="Path to a json configuration file with keys clientId and clientSecret",
)
parser.add_argument(
"--no-notifications",
action="store_true",
help="Disable desktop notifications",
)
ns = parser.parse_args(args)
cfg = Config()
if (cfg_file := ns.config_file or getenv("SPOTIFY_SHORTCUTS_CONFIG", None)) != None:
cfg = Config.from_file(Path(cfg_file))
return Config(
cache_file=ns.cache_file or cfg.cache_file,
client_id=ns.client_id or cfg.client_id,
client_secret=ns.client_secret or cfg.client_secret,
notifications=not ns.no_notifications or cfg.notifications,
)

View File

@@ -1,7 +0,0 @@
from spotify_shortcuts.spotify_like import SpotifyLike
from spotify_shortcuts.spotify_pl_add import SpotifyPlAdd
SHORTCUT_REGISTRY = {
"like": SpotifyLike(),
"pl_add": SpotifyPlAdd(),
}

View File

@@ -1,47 +0,0 @@
from spotify_shortcuts.config import Config, load_config
from spotify_shortcuts.shortcut import Shortcut
from spotify_shortcuts.spotify_auth import authenticated_session
from itertools import chain
def all_scopes() -> list[str]:
from spotify_shortcuts.registry import SHORTCUT_REGISTRY
return list(
set(
chain.from_iterable(
shortcut.get_scopes() for shortcut in SHORTCUT_REGISTRY.values()
)
)
)
def run_shortcut(shortcut: Shortcut, config: Config):
client = authenticated_session(
config, scopes=all_scopes() # use all scopes to avoid re-authentication
)
shortcut.execute(client, config)
def main():
from spotify_shortcuts.registry import SHORTCUT_REGISTRY
import sys
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <shortcut_name>")
sys.exit(1)
shortcut_name = sys.argv[1]
if shortcut_name not in SHORTCUT_REGISTRY:
print(f"Shortcut '{shortcut_name}' not found.")
sys.exit(1)
shortcut = SHORTCUT_REGISTRY[shortcut_name]
config = load_config(args=sys.argv[2:])
run_shortcut(shortcut, config)
if __name__ == "__main__":
main()

View File

@@ -1,21 +0,0 @@
from abc import ABC, abstractmethod
from spotify_shortcuts.config import Config
from spotipy import Spotify
class Shortcut(ABC):
@abstractmethod
def execute(self, client: Spotify, config: Config):
"""Execute the shortcut action."""
pass
@abstractmethod
def get_help(self) -> str:
"""Return a description of the shortcut."""
pass
@abstractmethod
def get_scopes(self) -> list[str]:
"""Return the spotify API scopes required for the shortcut."""
pass

View File

@@ -1,20 +0,0 @@
from spotipy.oauth2 import SpotifyOAuth
from spotipy import Spotify
from spotify_shortcuts.config import Config
CALLBACK_URI = "http://127.0.0.1:45632/callback"
def authenticated_session(cfg: Config, scopes: list[str]) -> Spotify:
assert cfg.client_id, "Spotify client ID is required"
assert cfg.client_secret, "Spotify client secret is required"
return Spotify(
auth_manager=SpotifyOAuth(
client_id=cfg.client_id,
client_secret=cfg.client_secret,
redirect_uri=CALLBACK_URI,
scope=" ".join(scopes),
cache_path=cfg.cache_file,
)
)

View File

@@ -1,46 +0,0 @@
from spotify_shortcuts.run import run_shortcut
from spotify_shortcuts.shortcut import Shortcut
from spotify_shortcuts.config import load_config
from desktop_notifier import DesktopNotifierSync
SCOPES = [
"user-library-read",
"user-library-modify",
"user-read-playback-state",
]
class SpotifyLike(Shortcut):
def execute(self, client, config):
if (playback := client.current_playback()) is None:
print("No current playback found.")
return
if (uri := playback.get("item", {}).get("uri", None)) is None:
print("No track URI found in current playback.")
return
client.current_user_saved_tracks_add(tracks=[uri])
if config.notifications:
dn = DesktopNotifierSync()
dn.send(
title="Track Liked",
message=f"Track \"{playback.get('item', {}).get('name', '<no-name>')}\" by \"{
", ".join(a.get('name', '<no-name>') for a in playback.get('item', {}).get('artists', []))
}\" has been liked.",
)
def get_help(self) -> str:
return "Like the currently playing track."
def get_scopes(self) -> list[str]:
return SCOPES
def main():
run_shortcut(SpotifyLike(), load_config())
if __name__ == "__main__":
main()

View File

@@ -1,57 +0,0 @@
from spotify_shortcuts.shortcut import Shortcut
from spotify_shortcuts.config import load_config
from desktop_notifier import DesktopNotifierSync
from spotify_shortcuts.run import run_shortcut
SCOPES = [
"playlist-modify-public",
"playlist-modify-private",
"user-read-playback-state",
]
class SpotifyPlAdd(Shortcut):
def execute(self, client, config):
if (playback := client.current_playback()) is None:
print("No current playback found.")
return
if (track_uri := playback.get("item", {}).get("uri", None)) is None:
print("No track URI found in current playback.")
return
if (context_uri := playback.get("context", {}).get("uri", None)) is None:
print("No context URI found in current playback.")
return
client.playlist_add_items(context_uri, items=[track_uri])
if config.notifications:
track_name = playback.get("item", {}).get("name", "<no-name>")
artists = ", ".join(
a.get("name", "<no-name>")
for a in playback.get("item", {}).get("artists", [])
)
playlist_name = (client.playlist(context_uri) or {}).get(
"name", "<no-name>"
)
dn = DesktopNotifierSync()
dn.send(
title="Track Added to Playlist",
message=f'Track "{track_name}" by "{artists}" has been added to {playlist_name}.',
)
def get_help(self) -> str:
return "Add the currently playing track to the current playlist."
def get_scopes(self) -> list[str]:
return SCOPES
def main():
run_shortcut(SpotifyPlAdd(), load_config())
if __name__ == "__main__":
main()

View File

@@ -1 +0,0 @@
use flake ../../#bulk-transcode --show-trace

View File

@@ -1,96 +0,0 @@
#!/usr/bin/env bash
declare -rA presets=(
[davinci-resolve]="-c:v dnxhd -profile:v dnxhr_hq -pix_fmt yuv422p -c:a pcm_s16le"
[instagram]="-vf scale='if(gte(iw/ih,1),1920,-1)':'if(gte(iw/ih,1),-1,1920)':flags=lanczos -r 30 -c:v libx264 -profile:v high -level 4.1 -pix_fmt yuv420p -preset slow -crf 18 -bf 2 -g 15 -keyint_min 15 -x264-params \"open-gop=0:cabac=1:b-pyramid=none\" -movflags +faststart -c:a aac -b:a 96k"
[insta-4k]="-r 30 -c:v libx264 -profile:v high -level 4.1 -pix_fmt yuv420p -preset slow -crf 18 -bf 2 -g 15 -keyint_min 15 -x264-params \"open-gop=0:cabac=1:b-pyramid=none\" -movflags +faststart -c:a aac -b:a 96k"
[storage-hevc]="-c:v libx265 -preset slower -crf 18 -pix_fmt yuv420p10le -x265-params aq-mode=3:aq-strength=1.0:psy-rd=1.8:psy-rdoq=1.0 -c:a copy"
[storage-av1]="-c:v libsvtav1 -preset 6 -crf 28 -pix_fmt yuv420p -g 240 -svtav1-params tune=0:aq-mode=2 -c:a copy"
[storage-av1-1080p]="-vf scale='if(gte(iw/ih,1),1920,-1)':'if(gte(iw/ih,1),-1,1920)' -c:v libsvtav1 -preset 6 -crf 28 -pix_fmt yuv420p -g 240 -svtav1-params tune=0:aq-mode=2 -c:a copy"
[storage-av1-nvenc]="-c:v av1_nvenc -cq 28 -preset slow -pix_fmt yuv420p10le -c:a copy"
[network]="-c:v libx264 -preset slow -crf 22 -pix_fmt yuv420p -c:a aac -b:a 128k"
[network-1080p]="-vf scale='if(gte(iw/ih,1),1920,-1)':'if(gte(iw/ih,1),-1,1920)' -c:v libx264 -preset slow -crf 22 -pix_fmt yuv420p -c:a aac -b:a 128k"
[whatsapp]="-vf scale='if(gte(iw/ih,1),1920,-1)':'if(gte(iw/ih,1),-1,1920)' -c:v libx264 -preset slow -crf 30 -profile:v baseline -level 3.0 -pix_fmt yuv420p -r 25 -g 50 -c:a aac -b:a 160k -r:a 44100"
)
declare -rA containers=(
[davinci-resolve]="mov"
[instagram]="mp4"
[insta-4k]="mp4"
[storage-hevc]="mkv"
[storage-av1]="mkv"
[storage-av1-1080p]="mkv"
[storage-av1-nvenc]="mkv"
[network]="mp4"
[network-1080p]="mp4"
[whatsapp]="mp4"
)
where="${1:-.}"
dest="${2:-$where}"
selection=$(find "$where" -type f | fzf --multi --preview 'ffprobe -v error -show_format -show_streams {}' --preview-window=up:wrap)
preset=$(
printf '%s\n' "${!presets[@]}" | \
fzf --multi --prompt "Select a preset"
)
flags="${presets[$preset]}"
container="${containers[$preset]}"
output_dir=$(find "$dest" -type d ! -name '.*' ! -path '*/.*/*' | fzf --preview 'tree -C {}' --preview-window=up:wrap --prompt "Select output directory: ")
if gum confirm "Flatten the directory structure?";
then
flatten=true
else
flatten=false
fi
function transcode_job {
local ifile="$1"
local output_dir="$2"
local flatten="$3"
local where="$4"
local flags="$5"
local container="$6"
local fname=$(basename "$ifile")
local segment=$(realpath --relative-to="$where" "$ifile")
if [ "$flatten" = true ]; then
output_file="$output_dir/$fname.$container"
else
output_file="$output_dir/$segment.$container"
fi
tmp_file=$(mktemp)
echo "Running Command: ffmpeg -y -i $ifile $flags $output_file" >> "$tmp_file"
mkdir -p "$(dirname "$output_file")"
if ffmpeg -y -i "$ifile" $(echo -n "$flags") "$output_file" 2>> "$tmp_file";
then
rm -f "$tmp_file"
else
# gum log "Failed to transcode $ifile. Check ./error.log for details."
cat "$tmp_file" >> error.log
rm -f "$tmp_file"
fi
}
export -f transcode_job
mapfile -t files <<< "$selection"
len=${#files[@]}
i=1
for file in "${files[@]}"; do
if [[ -f "$file" ]]; then
gum spin --spinner dot --title "[$i/$len] Transcoding $file" -- bash -c "source <(declare -f transcode_job); transcode_job \"$file\" \"$output_dir\" \"$flatten\" \"$where\" \"$flags\" \"$container\""
else
echo "Skipping invalid file: $file" >&2
fi
((i++))
done

View File

@@ -1,35 +0,0 @@
{
bash,
coreutils,
ffmpeg,
findutils,
fzf,
gum,
lib,
makeWrapper,
stdenv,
tree,
...
}:
stdenv.mkDerivation (finalAttrs: {
name = "bulk-transcode";
src = ./.;
buildInputs = [
bash
coreutils
ffmpeg
findutils
fzf
gum
makeWrapper
tree
];
installPhase = ''
install -Dm755 bulk-transcode.sh $out/bin/bulk-transcode
wrapProgram $out/bin/bulk-transcode \
--set PATH "${lib.makeBinPath finalAttrs.buildInputs}"
'';
})

View File

@@ -1,7 +0,0 @@
{pkgs ? import <nixpkgs> {}}: let
bin = pkgs.callPackage ./default.nix {};
in
pkgs.mkShell {
name = "bulk-transcode";
inputsFrom = [bin];
}

View File

@@ -1,40 +0,0 @@
{
appimageTools,
fetchurl,
makeWrapper,
...
}: let
pname = "crossover";
version = "3.1.5";
src = fetchurl {
url = "https://github.com/lacymorrow/crossover/releases/download/v${version}/CrossOver-${version}-x86_64.AppImage";
sha256 = "sha256-64RPal8n1PJh1LB+CTyNFt04Pw1lVgcsyc63S8yQ/DA=";
};
appimageContents = appimageTools.extract {
inherit pname version src;
};
in
appimageTools.wrapType2 {
inherit pname version src;
nativeBuildInputs = [makeWrapper];
extraInstallCommands = ''
wrapProgram $out/bin/${pname} --add-flags "--no-sandbox"
# Create a minimal .desktop file manually
mkdir -p $out/share/applications
cat > $out/share/applications/${pname}.desktop <<EOF
[Desktop Entry]
Name=${pname}
Exec=${pname} %U
Icon=${pname}
Type=Application
Categories=Utility;
EOF
# Optionally extract icon from AppImage (if available)
# You can also manually install an icon here:
mkdir -p $out/share/icons/hicolor/0x0/apps
cp ${appimageContents}/usr/share/icons/hicolor/0x0/apps/${pname}.png $out/share/icons/hicolor/0x0/apps/${pname}.png || true
'';
}

View File

@@ -1,8 +0,0 @@
final: _: {
hive = {
crossover = final.callPackage ./crossover.nix {};
bulk-transcode = final.callPackage ./bulk-transcode {};
spotify-shortcuts = final.callPackage ./spotify-shortcuts/derivation.nix {};
layan-qt6 = final.kdePackages.callPackage ./layan-qt6.nix {};
};
}

View File

@@ -1,52 +0,0 @@
{
stdenv,
lib,
fetchFromGitHub,
gitUpdater,
kdeclarative,
libplasma,
plasma-workspace,
}:
stdenv.mkDerivation {
pname = "layan-kde-qt6";
version = "2025-02-13";
src = fetchFromGitHub {
owner = "vinceliuice";
repo = "Layan-kde";
rev = "ace0b1d93e5f08c650630c146b2d637e1e2e6cd1";
hash = "sha256-T69bGjfZeOsJLmOZKps9N2wMv5VKYeo1ipGEsLAS+Sg=";
};
# Propagate sddm theme dependencies to user env otherwise sddm does
# not find them. Putting them in buildInputs is not enough.
propagatedUserEnvPkgs = [
kdeclarative
libplasma
plasma-workspace
];
postPatch = ''
patchShebangs install.sh
substituteInPlace install.sh \
--replace '$HOME/.local' $out \
--replace '$HOME/.config' $out/share
'';
installPhase = ''
runHook preInstall
name= ./install.sh --dest $out/share/themes
runHook postInstall
'';
passthru.updateScript = gitUpdater {};
meta = with lib; {
description = "Flat Design theme for KDE Plasma desktop";
homepage = "https://github.com/vinceliuice/Layan-kde";
license = licenses.gpl3Only;
platforms = platforms.all;
};
}

View File

@@ -1 +0,0 @@
use flake ../../#spotify-shortcuts --show-trace

View File

@@ -1,2 +0,0 @@
{pkgs ? import <nixpkgs> {}}:
pkgs.callPackage ./derivation.nix {}

View File

@@ -1,9 +0,0 @@
{python3Packages}:
with python3Packages;
buildPythonApplication {
name = "spotify-shortcuts";
propagatedBuildInputs = [spotipy pyxdg desktop-notifier];
pyproject = true;
build-system = [setuptools];
src = ./.;
}

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env python
from setuptools import setup, find_packages
setup(
name="spotify_shortcuts",
version="1.0",
packages=find_packages(),
entry_points={
"console_scripts": [
"spotisc=spotify_shortcuts.run:main",
"spotify-like=spotify_shortcuts.spotify_like:main",
"spotify-pl-add=spotify_shortcuts.spotify_pl_add:main",
],
},
)

View File

@@ -1,15 +0,0 @@
{pkgs ? import <nixpkgs> {}}: let
drv = pkgs.callPackage ./derivation.nix {};
in
pkgs.mkShell {
packages = [
pkgs.pyright
pkgs.black
];
inputsFrom = [drv];
shellHook = ''
export PYTHONPATH="$PYTHONPATH:$(pwd)"
'';
}

View File

@@ -1,4 +0,0 @@
from spotify_shortcuts.run import main
if __name__ == "__main__":
main()

View File

@@ -1,74 +0,0 @@
from pathlib import Path
from dataclasses import dataclass
from argparse import ArgumentParser
from typing import List
from os import getenv
from sys import argv
from xdg.BaseDirectory import xdg_cache_home
from json import loads
@dataclass
class Config:
cache_file: Path = Path(xdg_cache_home) / Path("spotify-shortcuts.json")
client_id: str | None = None
client_secret: str | None = None
notifications: bool = True
@staticmethod
def from_file(path: Path):
if not path.exists():
raise FileNotFoundError(f"Configuration file {path} does not exist.")
with open(path, "r") as f:
data = loads(f.read())
return Config(
cache_file=Path(data.get("cacheFile", Config.cache_file)),
client_id=data.get("clientId", Config.client_id),
client_secret=data.get("clientSecret", Config.client_secret),
)
def load_config(args: List[str] = argv[1:]) -> Config:
parser = ArgumentParser(description="Spotify CLI Tool")
parser.add_argument(
"--cache-file",
type=Path,
default=Config.cache_file,
help="Path to the cache file for Spotify authentication",
)
parser.add_argument(
"--client-id",
type=str,
default=Config.client_id,
help="Spotify API Client ID",
)
parser.add_argument(
"--client-secret",
type=str,
default=Config.client_secret,
help="Spotify API Client Secret",
)
parser.add_argument(
"--config-file",
type=str,
help="Path to a json configuration file with keys clientId and clientSecret",
)
parser.add_argument(
"--no-notifications",
action="store_true",
help="Disable desktop notifications",
)
ns = parser.parse_args(args)
cfg = Config()
if (cfg_file := ns.config_file or getenv("SPOTIFY_SHORTCUTS_CONFIG", None)) != None:
cfg = Config.from_file(Path(cfg_file))
return Config(
cache_file=ns.cache_file or cfg.cache_file,
client_id=ns.client_id or cfg.client_id,
client_secret=ns.client_secret or cfg.client_secret,
notifications=not ns.no_notifications or cfg.notifications,
)

View File

@@ -1,7 +0,0 @@
from spotify_shortcuts.spotify_like import SpotifyLike
from spotify_shortcuts.spotify_pl_add import SpotifyPlAdd
SHORTCUT_REGISTRY = {
"like": SpotifyLike(),
"pl_add": SpotifyPlAdd(),
}

View File

@@ -1,47 +0,0 @@
from spotify_shortcuts.config import Config, load_config
from spotify_shortcuts.shortcut import Shortcut
from spotify_shortcuts.spotify_auth import authenticated_session
from itertools import chain
def all_scopes() -> list[str]:
from spotify_shortcuts.registry import SHORTCUT_REGISTRY
return list(
set(
chain.from_iterable(
shortcut.get_scopes() for shortcut in SHORTCUT_REGISTRY.values()
)
)
)
def run_shortcut(shortcut: Shortcut, config: Config):
client = authenticated_session(
config, scopes=all_scopes() # use all scopes to avoid re-authentication
)
shortcut.execute(client, config)
def main():
from spotify_shortcuts.registry import SHORTCUT_REGISTRY
import sys
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <shortcut_name>")
sys.exit(1)
shortcut_name = sys.argv[1]
if shortcut_name not in SHORTCUT_REGISTRY:
print(f"Shortcut '{shortcut_name}' not found.")
sys.exit(1)
shortcut = SHORTCUT_REGISTRY[shortcut_name]
config = load_config(args=sys.argv[2:])
run_shortcut(shortcut, config)
if __name__ == "__main__":
main()

View File

@@ -1,21 +0,0 @@
from abc import ABC, abstractmethod
from spotify_shortcuts.config import Config
from spotipy import Spotify
class Shortcut(ABC):
@abstractmethod
def execute(self, client: Spotify, config: Config):
"""Execute the shortcut action."""
pass
@abstractmethod
def get_help(self) -> str:
"""Return a description of the shortcut."""
pass
@abstractmethod
def get_scopes(self) -> list[str]:
"""Return the spotify API scopes required for the shortcut."""
pass

View File

@@ -1,20 +0,0 @@
from spotipy.oauth2 import SpotifyOAuth
from spotipy import Spotify
from spotify_shortcuts.config import Config
CALLBACK_URI = "http://127.0.0.1:45632/callback"
def authenticated_session(cfg: Config, scopes: list[str]) -> Spotify:
assert cfg.client_id, "Spotify client ID is required"
assert cfg.client_secret, "Spotify client secret is required"
return Spotify(
auth_manager=SpotifyOAuth(
client_id=cfg.client_id,
client_secret=cfg.client_secret,
redirect_uri=CALLBACK_URI,
scope=" ".join(scopes),
cache_path=cfg.cache_file,
)
)

View File

@@ -1,46 +0,0 @@
from spotify_shortcuts.run import run_shortcut
from spotify_shortcuts.shortcut import Shortcut
from spotify_shortcuts.config import load_config
from desktop_notifier import DesktopNotifierSync
SCOPES = [
"user-library-read",
"user-library-modify",
"user-read-playback-state",
]
class SpotifyLike(Shortcut):
def execute(self, client, config):
if (playback := client.current_playback()) is None:
print("No current playback found.")
return
if (uri := playback.get("item", {}).get("uri", None)) is None:
print("No track URI found in current playback.")
return
client.current_user_saved_tracks_add(tracks=[uri])
if config.notifications:
dn = DesktopNotifierSync()
dn.send(
title="Track Liked",
message=f"Track \"{playback.get('item', {}).get('name', '<no-name>')}\" by \"{
", ".join(a.get('name', '<no-name>') for a in playback.get('item', {}).get('artists', []))
}\" has been liked.",
)
def get_help(self) -> str:
return "Like the currently playing track."
def get_scopes(self) -> list[str]:
return SCOPES
def main():
run_shortcut(SpotifyLike(), load_config())
if __name__ == "__main__":
main()

View File

@@ -1,57 +0,0 @@
from spotify_shortcuts.shortcut import Shortcut
from spotify_shortcuts.config import load_config
from desktop_notifier import DesktopNotifierSync
from spotify_shortcuts.run import run_shortcut
SCOPES = [
"playlist-modify-public",
"playlist-modify-private",
"user-read-playback-state",
]
class SpotifyPlAdd(Shortcut):
def execute(self, client, config):
if (playback := client.current_playback()) is None:
print("No current playback found.")
return
if (track_uri := playback.get("item", {}).get("uri", None)) is None:
print("No track URI found in current playback.")
return
if (context_uri := playback.get("context", {}).get("uri", None)) is None:
print("No context URI found in current playback.")
return
client.playlist_add_items(context_uri, items=[track_uri])
if config.notifications:
track_name = playback.get("item", {}).get("name", "<no-name>")
artists = ", ".join(
a.get("name", "<no-name>")
for a in playback.get("item", {}).get("artists", [])
)
playlist_name = (client.playlist(context_uri) or {}).get(
"name", "<no-name>"
)
dn = DesktopNotifierSync()
dn.send(
title="Track Added to Playlist",
message=f'Track "{track_name}" by "{artists}" has been added to {playlist_name}.',
)
def get_help(self) -> str:
return "Add the currently playing track to the current playlist."
def get_scopes(self) -> list[str]:
return SCOPES
def main():
run_shortcut(SpotifyPlAdd(), load_config())
if __name__ == "__main__":
main()

View File

@@ -1 +0,0 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILNYmQ4HspLsunLyryI/ZbX4tTuRI6cRv0n/ai6+DNCc borg@jroeger.de

View File

@@ -1,448 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: Hostname:
Version: Hockeypuck 2.1.1-10-gec3b0e7
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
=JVtR
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -1 +0,0 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBodNTvEGT2J+w6qpJNvmwZuHCshzq2nwU92+VqRcyn4 jonas@comfy-station

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 642 KiB

View File

@@ -1 +0,0 @@
build

View File

@@ -1,10 +0,0 @@
cmake_minimum_required(VERSION 3.15)
project(Hello
DESCRIPTION "Hello World"
LANGUAGES C
)
add_executable(hello src/main.c)
install(TARGETS hello)

View File

@@ -1,56 +0,0 @@
{
description = "Cmake C Flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
};
outputs = inputs @ {
self,
flake-parts,
...
}:
flake-parts.lib.mkFlake {inherit inputs;} (
top: {
imports = [];
flake = {
overlays.default = final: prev: {
my-derivation = final.callPackage ./nix/derivation.nix {};
};
};
systems = [
"x86_64-linux"
];
perSystem = {
self',
pkgs,
system,
...
}: {
_module.args.pkgs = import inputs.nixpkgs {
inherit system;
overlays = [self.overlays.default];
};
packages.default = pkgs.my-derivation;
devShells.default = pkgs.mkShell {
packages = [
pkgs.cmake-language-server
pkgs.cmake-format
pkgs.clang-tools
pkgs.gdb
];
inputsFrom = [self'.packages.default];
shellHook = ''
export CMAKE_EXPORT_COMPILE_COMMANDS=ON
'';
};
};
}
);
}

View File

@@ -1,17 +0,0 @@
{
cmake,
stdenv,
...
}:
stdenv.mkDerivation (finalAttrs: {
pname = "my-derivation";
version = "0.1.0";
src = ../.;
nativeBuildInputs = [cmake];
buildInputs = [];
meta = {
description = "Hello World Binary";
mainProgram = "hello";
};
})

View File

@@ -1,6 +0,0 @@
#include <stdio.h>
int main() {
puts("Hello, Flake!");
return 0;
}

View File

@@ -1,17 +0,0 @@
#!/usr/bin/env bash
# ^ make editor happy
#
# Use https://direnv.net/ to automatically load the dev shell.
#
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4="
fi
watch_file nix/**
watch_file -- **/*.nix
# Adding files to git includes them in a flake
# But it is also a bit much reloading.
# watch_file .git/index .git/HEAD
use flake . --show-trace

View File

@@ -1,3 +0,0 @@
.direnv/
target/
result/

View File

@@ -1,11 +0,0 @@
[package]
name = "hello"
description = "A test rust binary"
publish = false
version = "0.1.0"
edition = "2021"
[[bin]]
name = "hello"
path = "src/main.rs"

View File

@@ -1,61 +0,0 @@
{
description = "Rust-Hello";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
crate2nix = {
url = "github:nix-community/crate2nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = inputs @ {
crate2nix,
flake-utils,
nixpkgs,
rust-overlay,
...
}:
flake-utils.lib.eachDefaultSystem (
system: let
# Overlay pkgs with rust-bin
overlays = [(import rust-overlay)];
pkgs = import nixpkgs {
inherit system overlays;
};
# Use rust-bin to generate the toolchain from rust-toolchain.toml
rust-toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
buildRustCrateForPkgs = _:
pkgs.buildRustCrate.override {
rustc = rust-toolchain; # Use rustc from toolchain
cargo = rust-toolchain; # Use cargo from toolchain
};
# Cargo.nix for IFD
generatedCargoNix = crate2nix.tools.${system}.generatedCargoNix {
name = "rustnix";
src = ./.;
};
cargoNix = import generatedCargoNix {
inherit pkgs buildRustCrateForPkgs;
};
in {
packages = rec {
hello = cargoNix.rootCrate.build;
default = hello;
};
devShell = pkgs.mkShell {
buildInputs = [rust-toolchain];
};
}
);
}

View File

@@ -1,3 +0,0 @@
[toolchain]
channel = "1.85.0"
components = [ "rustfmt", "rustc-dev", "rust-analyzer", "rust-src"]

View File

@@ -1,3 +0,0 @@
fn main() {
println!("{}", "Hello, world!");
}