Compare commits

...

3 Commits

Author SHA1 Message Date
valere
ef4c0f8747 adb music sync v2 2025-10-22 09:46:51 +02:00
valere
4ab5b3a2f1 bashrc new helpers fn 2025-10-22 09:45:47 +02:00
valere
323af82393 .linx-env v1 2025-10-07 09:34:46 +02:00
10 changed files with 532 additions and 57 deletions

View File

@@ -1,6 +0,0 @@
alias dlmusic='MUSIC_PATH="/mnt/media/music/evilspins/$(date +"%Y")" && mkdir -p $MUSIC_PATH && NOW=$(date +"%Y%m%d%H") && pip install -U yt-dlp && yt-dlp -f bestaudio -x --audio-format mp3 --audio-quality 0 -o $MUSIC_PATH"/"$NOW"__%(title)s__%(artist)s__%(id)s.%(ext)s" $1'
alias dlvideo='VIDEO_PATH="/mnt/media/video/autre" && cd "$VIDEO_PATH" && pip install -U yt-dlp && yt-dlp -f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4" --merge-output-format mp4 "$@"'
alias dlfilm='VIDEO_PATH="/mnt/media/video/film" && yt-dlp -o $VIDEO_PATH $1'
alias dlimage='IMAGE_PATH="/mnt/media/image/screenshit" && wget -P $IMAGE_PATH $1'
alias search='find ./ -iname "*$1*"'
alias searchinside='grep -rwl "*$1*" ./'

15
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}

View File

@@ -0,0 +1,246 @@
#!/usr/bin/env python3
import os
import sys
import time
import shlex
import subprocess
import tempfile
from pathlib import Path
from typing import List, Tuple, Optional
# --- Configuration ---
HOME = Path.home()
SOURCES = [
HOME / "Music",
HOME / "Downloads",
]
DEST_ON_PHONE = "/sdcard/Music"
# Model tag as seen in `adb devices -l` (e.g., ... model:AGM_M7 ...)
ADB_MODEL_MATCH = ["AGM_M7", "AGM", "M7"]
POLL_INTERVAL_SEC = 60
SIZE_LIMIT_BYTES = 7 * 1024 ** 3 # 7 GiB
MUSIC_EXTS = {".mp3", ".m4a", ".ogg", ".aac", ".opus", ".alac", ".flac"}
# If you have a static Wi-Fi ADB endpoint, set it here (e.g., "192.168.1.50:5555").
ADB_IP_PORT: Optional[str] = None
# --- Helpers ---
def run(cmd: List[str], check: bool = False, text: bool = True, timeout: Optional[int] = None) -> subprocess.CompletedProcess:
try:
return subprocess.run(cmd, check=check, text=text, capture_output=True, timeout=timeout)
except Exception as e:
return subprocess.CompletedProcess(cmd, returncode=1, stdout="", stderr=str(e))
def ensure_adb_server() -> None:
run(["adb", "start-server"]) # best-effort
def list_adb_devices() -> List[str]:
proc = run(["adb", "devices", "-l"]) # do not check, we parse regardless
if proc.returncode != 0:
return []
lines = [ln.strip() for ln in proc.stdout.splitlines()]
# skip header line: "List of devices attached"
if lines and lines[0].lower().startswith("list of devices"):
lines = lines[1:]
return [ln for ln in lines if ln]
def adb_connect_if_needed():
if not ADB_IP_PORT:
return
devices = list_adb_devices()
if any(ADB_IP_PORT in ln for ln in devices):
return # already connected
run(["adb", "connect", ADB_IP_PORT])
def is_agm_connected() -> bool:
devices = list_adb_devices()
for ln in devices:
if "device" in ln and not ln.endswith("offline"):
if any(tag in ln for tag in ADB_MODEL_MATCH):
return True
return False
def gather_music_files() -> List[Tuple[Path, Path, float, int]]:
# Returns list of tuples: (root_dir, file_path, mtime, size)
entries: List[Tuple[Path, Path, float, int]] = []
for root in SOURCES:
try:
if not root.exists():
continue
for dirpath, _, filenames in os.walk(root):
dpath = Path(dirpath)
for fn in filenames:
p = dpath / fn
if p.suffix.lower() not in MUSIC_EXTS:
continue
try:
st = p.stat()
entries.append((root, p, st.st_mtime, st.st_size))
except Exception:
continue
except Exception:
continue
# newest first by mtime
entries.sort(key=lambda t: t[2], reverse=True)
return entries
def select_up_to_size(entries: List[Tuple[Path, Path, float, int]], limit_bytes: int) -> List[Tuple[Path, Path, int]]:
selected: List[Tuple[Path, Path, int]] = []
total = 0
for root, p, _mt, sz in entries:
if total >= limit_bytes:
break
selected.append((root, p, sz))
total += sz
return selected
def adb_shell(cmd: str) -> subprocess.CompletedProcess:
# Single string executed via adb shell
return run(["adb", "shell", cmd])
def remote_size(adb_path: str) -> Optional[int]:
# Uses stat if available; returns None if missing
# Escape path for shell
qpath = shlex.quote(adb_path)
proc = adb_shell(f"stat -c %s {qpath}")
if proc.returncode != 0 or not proc.stdout.strip():
return None
out = proc.stdout.strip()
if "No such file" in out or "not found" in out:
return None
try:
return int(out.splitlines()[-1].strip())
except Exception:
return None
def ensure_remote_dir(adb_path: str) -> None:
# mkdir -p dirname
dirname = os.path.dirname(adb_path)
if not dirname:
return
adb_shell(f"mkdir -p {shlex.quote(dirname)}")
def have_ffmpeg() -> bool:
proc = run(["ffmpeg", "-version"])
return proc.returncode == 0
def transcode_flac_to_mp3(src: Path) -> Optional[Path]:
try:
tmp = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False)
tmp_path = Path(tmp.name)
tmp.close()
except Exception:
return None
cmd = [
"ffmpeg",
"-y",
"-i",
str(src),
"-vn",
"-c:a",
"libmp3lame",
"-q:a",
"2",
str(tmp_path),
]
proc = run(cmd)
if proc.returncode != 0:
try:
tmp_path.unlink(missing_ok=True) # type: ignore[arg-type]
except Exception:
pass
return None
return tmp_path
def push_file(local_path: Path, remote_path: str, size: int) -> bool:
# Skip if same size exists
rsz = remote_size(remote_path)
if rsz is not None and rsz == size:
print(f"[=] Skip (same size): {local_path} -> {remote_path}")
return True
ensure_remote_dir(remote_path)
proc = run(["adb", "push", "-p", str(local_path), remote_path])
if proc.returncode == 0:
print(f"[+] Pushed: {local_path} -> {remote_path}")
return True
else:
print(f"[!] Push failed: {local_path}\n{proc.stderr}")
return False
def sync_latest_batch():
entries = gather_music_files()
if not entries:
print("[i] No music files found.")
return
batch = select_up_to_size(entries, SIZE_LIMIT_BYTES)
print(f"[i] Selected {len(batch)} files up to {SIZE_LIMIT_BYTES/(1024**3):.2f} GiB")
for root, p, sz in batch:
try:
rel = p.relative_to(root)
except Exception:
rel = p.name # fallback
if p.suffix.lower() == ".flac":
if not have_ffmpeg():
print(f"[!] ffmpeg not available, skipping: {p}")
continue
mp3_tmp = transcode_flac_to_mp3(p)
if not mp3_tmp:
print(f"[!] Transcode failed, skipping: {p}")
continue
try:
if isinstance(rel, Path):
rel_mp3 = rel.with_suffix(".mp3")
remote = f"{DEST_ON_PHONE.rstrip('/')}/{rel_mp3.as_posix()}"
else:
remote = f"{DEST_ON_PHONE.rstrip('/')}/{Path(rel).with_suffix('.mp3').as_posix()}"
msz = mp3_tmp.stat().st_size
push_file(mp3_tmp, remote, msz)
finally:
try:
mp3_tmp.unlink()
except Exception:
pass
else:
remote = f"{DEST_ON_PHONE.rstrip('/')}/{rel.as_posix()}"
push_file(p, remote, sz)
def main():
print("[i] adb-music-sync daemon starting...")
ensure_adb_server()
connected_prev = False
while True:
try:
ensure_adb_server()
adb_connect_if_needed()
connected = is_agm_connected()
if connected and not connected_prev:
print("[+] AGM M7 connected. Starting sync of latest ~7 GiB...")
sync_latest_batch()
elif not connected and connected_prev:
print("[-] AGM M7 disconnected.")
connected_prev = connected
time.sleep(POLL_INTERVAL_SEC)
except KeyboardInterrupt:
print("[i] Exiting on user interrupt.")
sys.exit(0)
except Exception as e:
print(f"[!] Error in main loop: {e}")
time.sleep(POLL_INTERVAL_SEC)
if __name__ == "__main__":
main()

78
adb-music-sync/install.sh Normal file
View File

@@ -0,0 +1,78 @@
#!/usr/bin/env bash
set -euo pipefail
SILENT=false
LOG_PREFIX="[adb-music-sync]"
PYTHON_SCRIPT="$HOME/.linux-env/adb-music-sync/adb-music-sync.py"
INSTALL_TARGET="$HOME/.local/bin/adb-music-sync.py"
AUTOSTART_DIR="$HOME/.config/autostart"
AUTOSTART_FILE="$AUTOSTART_DIR/adb-music-sync.desktop"
log() {
if [ "$SILENT" = true ]; then return; fi
echo "$LOG_PREFIX $*"
}
check_and_install_deps() {
# Minimal dependency: adb
if ! command -v adb >/dev/null 2>&1; then
if command -v apt >/dev/null 2>&1; then
log "[+] Installation d'adb (apt) ..."
sudo apt update -y && sudo apt install -y adb
elif command -v dnf >/dev/null 2>&1; then
log "[+] Installation d'adb (dnf) ..."
sudo dnf install -y android-tools
elif command -v pacman >/dev/null 2>&1; then
log "[+] Installation d'adb (pacman) ..."
sudo pacman -Sy --noconfirm android-tools
else
log "[!] adb introuvable et gestionnaire de paquets inconnu. Installez adb manuellement."
fi
fi
}
install_script() {
check_and_install_deps
log "[+] Copie du script Python..."
mkdir -p "$(dirname "$INSTALL_TARGET")"
cp "$PYTHON_SCRIPT" "$INSTALL_TARGET"
chmod +x "$INSTALL_TARGET"
log "[+] Création du fichier autostart..."
mkdir -p "$AUTOSTART_DIR"
cat > "$AUTOSTART_FILE" <<EOF
[Desktop Entry]
Type=Application
Exec=$INSTALL_TARGET
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name=ADB Music Sync
Comment=Lance la synchronisation de ~5GiB de musique vers l'AGM M7 à la connexion ADB
EOF
log "[✓] Installation terminée. Le script se lancera automatiquement à l'ouverture de session."
}
uninstall_script() {
log "[+] Suppression du script Python et autostart..."
rm -f "$INSTALL_TARGET"
rm -f "$AUTOSTART_FILE"
log "[✓] Désinstallation terminée."
}
case "${1:-}" in
uninstall)
uninstall_script
;;
silent)
SILENT=true
uninstall_script &>/dev/null || true
install_script &>/dev/null
;;
*)
install_script
;;
esac

11
applications/install.sh Normal file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
# desktop apps
cp ~/.linux-env/applications/*/*.desktop ~/.local/share/applications/
cp ~/.linux-env/applications/*/*.sh ~/.local/bin/
cp ~/.linux-env/applications/*/*.svg ~/.local/share/icons/
chmod +x ~/.local/share/applications/*.desktop
chmod +x ~/.local/bin/*.sh
update-desktop-database ~/.local/share/applications/
gtk-update-icon-cache ~/.local/share/icons/ # inutile sauf si tu fais un vrai thème d'icônes

103
bashrc/.bashrc Normal file
View File

@@ -0,0 +1,103 @@
# .linux-env
dlvideo() {
VIDEO_PATH="/mnt/media/video/autre"
cd "$VIDEO_PATH" || return
pip install -U yt-dlp
yt-dlp -f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4" --merge-output-format mp4 "$@"
}
dlfilm() {
VIDEO_PATH="/mnt/media/video/film"
yt-dlp -o "$VIDEO_PATH/%(title)s.%(ext)s" "$1"
}
dlimage() {
IMAGE_PATH="/mnt/media/image/screenshit"
wget -P "$IMAGE_PATH" "$1"
}
dlcover() {
query_or_file="$1"
manual_url="$2"
MUSIC_DIR="$HOME/media/files/music"
if [ -f "$query_or_file" ]; then
file="$query_or_file"
elif echo "$query_or_file" | grep -q '%(ext)'; then
file="$query_or_file"
else
file=$(find "$MUSIC_DIR" -type f \
\( -iname "*${query_or_file}*.mp3" -o -iname "*${query_or_file}*.m4a" -o -iname "*${query_or_file}*.flac" -o -iname "*${query_or_file}*.wav" -o -iname "*${query_or_file}*.ogg" \) \
| head -n 1)
fi
if [ -z "$file" ]; then
echo "❌ Aucun fichier trouvé pour: $query_or_file"
return 1
fi
base="${file%.*}"
if [ -n "$manual_url" ]; then
curl -s "$manual_url" -o "${base}.jpg"
echo "${base}.jpg saved from manual URL (fichier: $file)"
return
fi
artist_raw=$(echo "$base" | awk -F'__' '{print $2}')
title_raw=$(echo "$base" | awk -F'__' '{for(i=3;i<=NF;i++){printf (i>3?" ":""); printf $i}}')
artist=$(echo "$artist_raw" | tr '_' ' ')
title=$(echo "$title_raw" | tr '_' ' ')
query_enc=$(jq -rn --arg s "$artist $title" '$s|@uri')
it_url="https://itunes.apple.com/search?term=${query_enc}&entity=song&limit=1"
art=$(curl -s "$it_url" | jq -r '.results[0].artworkUrl100')
if [ -n "$art" ] && [ "$art" != "null" ]; then
art_hi=$(echo "$art" | sed 's/100x100bb/1000x1000bb/')
curl -s "$art_hi" -o "${base}.jpg"
echo "${base}.jpg saved from iTunes (fichier: $file)"
return
fi
api_key="5ae5d1212599f96ffa799e21da1b2a7a38274c94bb5ae8ad26ce8d5b08528aaa"
url=$(curl -s "https://serpapi.com/search.json?engine=google&q=${query_enc}&tbm=isch&api_key=${api_key}" \
| jq -r '.images_results[0].original')
if [ -n "$url" ] && [ "$url" != "null" ]; then
curl -s "$url" -o "${base}.jpg"
echo "${base}.jpg saved from Google Images (fichier: $file)"
else
echo "❌ No cover found for $artist - $title (fichier: $file)"
fi
}
dlmusic() {
MUSIC_PATH="/home/root/media/files/music"
mkdir -p "$MUSIC_PATH"
NOW=$(date +"%Y%m%d%H")
URL="$1"
# Récupère le titre
TITLE=$(yt-dlp --get-title "$URL")
# Transforme le titre pour le nom de fichier
SAFE_TITLE="${TITLE// - /__}"
SAFE_TITLE="${SAFE_TITLE// /_}"
TRACK_NAME="$MUSIC_PATH/${NOW}__${SAFE_TITLE}.%(ext)s"
# Téléchargement
yt-dlp -x --audio-format mp3 -o $TRACK_NAME "$URL"
dlcover $TRACK_NAME
}
search() {
find ./ -iname "*$1*"
}
searchinside() {
grep -rwl "$1" ./
}
# .linux-env

4
bashrc/install.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
sed -i '/# .linux-env/,/# .linux-env/d' ~/.bashrc
cat ~/.linux-env/bashrc/.bashrc >> ~/.bashrc

View File

@@ -1,13 +0,0 @@
[Unit]
Description=Pad Dolphin Watcher Service (User)
After=graphical.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /home/%u/.local/bin/gamecube-pad.py
Restart=always
RestartSec=3
WorkingDirectory=/home/%u
[Install]
WantedBy=default.target

View File

@@ -1,46 +1,77 @@
#!/bin/bash
set -e
SERVICE_NAME="gamecube-pad.service"
SERVICE_PATH="$HOME/.config/systemd/user/gamecube-pad.service"
PYTHON_SCRIPT="/home/$USER/.local/bin/gamecube-pad.py"
PYTHON_SCRIPT="$HOME/.local/bin/gamecube-pad.py"
AUTOSTART_DIR="$HOME/.config/autostart"
AUTOSTART_FILE="$AUTOSTART_DIR/gamecube-pad.desktop"
install_service() {
echo "[+] Installation des dépendances..."
sudo apt install -y python3-evdev python3-uinput xdotool wmctrl
SILENT=false
if [[ "$1" == "silent" ]]; then
SILENT=true
fi
echo "[+] Copie du script Python..."
mkdir -p "$(dirname "$PYTHON_SCRIPT")"
cp gamecube-pad.py "$PYTHON_SCRIPT"
chmod +x "$PYTHON_SCRIPT"
echo "[+] Copie du fichier systemd utilisateur..."
mkdir -p "$HOME/.config/systemd/user"
cp gamecube-pad.service "$SERVICE_PATH"
echo "[+] Activation du service..."
systemctl --user daemon-reload
systemctl --user enable gamecube-pad.service
systemctl --user restart gamecube-pad.service
echo "[✓] Installation terminée. Consultez les logs avec : journalctl --user -u gamecube-pad.service -f"
log() {
if [ "$SILENT" = false ]; then
echo "$@"
fi
}
uninstall_service() {
echo "[+] Arrêt et suppression du service..."
systemctl --user stop gamecube-pad.service || true
systemctl --user disable gamecube-pad.service || true
rm -f "$SERVICE_PATH"
check_and_install_deps() {
for pkg in evdev uinput; do
if ! python3 -c "import $pkg" &>/dev/null; then
log "[!] Dépendance manquante : $pkg. Installation avec pip..."
python3 -m pip install --user "$pkg" &>/dev/null
fi
done
for cmd in xdotool wmctrl; do
if ! command -v "$cmd" &>/dev/null; then
log "[!] Commande manquante : $cmd. Merci de linstaller via votre gestionnaire de paquets."
fi
done
}
install_script() {
check_and_install_deps
log "[+] Copie du script Python..."
mkdir -p "$(dirname "$PYTHON_SCRIPT")"
cp "$HOME/.linux-env/gamecube-pad/gamecube-pad.py" "$PYTHON_SCRIPT"
chmod +x "$PYTHON_SCRIPT"
log "[+] Création du fichier autostart..."
mkdir -p "$AUTOSTART_DIR"
cat > "$AUTOSTART_FILE" <<EOF
[Desktop Entry]
Type=Application
Exec=$PYTHON_SCRIPT
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name=GameCube Pad
Comment=Lance le script GameCube Pad au démarrage
EOF
log "[✓] Installation terminée. Le script se lancera automatiquement à l'ouverture de session."
}
uninstall_script() {
log "[+] Suppression du script Python et autostart..."
rm -f "$PYTHON_SCRIPT"
systemctl --user daemon-reload
echo "[✓] Désinstallation terminée."
rm -f "$AUTOSTART_FILE"
log "[✓] Désinstallation terminée."
}
case "$1" in
uninstall)
uninstall_service
uninstall_script
;;
silent)
SILENT=true
uninstall_script &>/dev/null
install_script &>/dev/null
;;
*)
install_service
install_script
;;
esac

View File

@@ -1,10 +1,16 @@
# desktop apps
cp ./applications/*/*.desktop ~/.local/share/applications/
cp ./applications/*/*.sh ~/.local/bin/
cp ./applications/*/*.svg ~/.local/share/icons/
#!/bin/bash
set -e
chmod +x ~/.local/share/applications/*.desktop
chmod +x ~/.local/bin/*.sh
# Chemin de base : le dossier où se trouve ce script
BASE_DIR="$(dirname "$(realpath "$0")")"
update-desktop-database ~/.local/share/applications/
# gtk-update-icon-cache ~/.local/share/icons/ # inutile sauf si tu fais un vrai thème d'icônes
echo "[+] Recherche et exécution des install.sh..."
# Boucle récursive sur tous les fichiers install.sh
find "$BASE_DIR" -type f -name "install.sh" ! -path "$BASE_DIR/install.sh" | while read -r script; do
echo "[+] Lancement : $script"
chmod +x "$script"
"$script"
done
echo "[✓] Tous les install.sh ont été exécutés."