Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions docs/faq/go2rtc-troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ If you're using `gphoto2` this issue is generally addressed inside the troublesh

The preview is created using gphoto2 and streamed by go2rtc. In some rare cases, the preview may not be fully stopped when the capture is triggered. Usually, it's just milliseconds that cause problems. Continue reading on [Delay capture](#delay-capture) to know how to fix possible timing issues.

### Error: Device is busy

If the preview is running, the camera may be busy when trying to capture an image. This can lead to errors like "Device is busy" or "Could not claim the USB device". There is a bug in go2rtc startingF
by version 1.9.10 which keeps the camera in use even after stopping the preview.
Try to downgrade to version 1.9.9 as a workaround:

```
sudo bash install-photobooth.sh
choose 4 go2rtc
choose 5 updat or downgrade go2rtc only
chose 1.9.9
```

#### Delay capture

We can use a tiny pause between stopping the preview and starting the capture to work around this:
Expand Down Expand Up @@ -144,6 +157,13 @@ The full configuration documentation can be found [here](https://github.com/Alex

While setting up go2rtc using the Photobooth Setup Wizard we're creating a default configuration which covers a wide range of devices. It might be, that your camera does not work out of the box with the default configuration and requires some extra work to be used on Photobooth. You're always welcome to contribute camera specific notes not listed below.

Dont forget to restart go2rtc after changing the configuration!

```ell
sudo systemctl stop go2rtc
sudo systemctl start go2rtc
```

### Raspberry Pi Camera Modules

- Default preview width and height match the Pi Camera v3. You might need to adjust the width and height inside the go2rtc configuration to matching values for your camera module.
Expand All @@ -163,9 +183,14 @@ log:
exec: trace
```

Now restart go2rtc to apply your changes:
### Canon EOS EOS40D

```ell
sudo systemctl stop go2rtc
sudo systemctl start go2rtc
```
streams:
photobooth:
- "exec:gphoto2 --capture-movie --stdout#input=mjpeg#fps=8#scale=640:480#pause-on-snapshot=true"

log:
exec: trace
```

290 changes: 290 additions & 0 deletions docs/scripts/auto-copy-to-usb-ntfs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
# Auto copy zu USB NTFS formatiert

Auto copy script to copy images and temp files to an USB drive formatted with NTFS when plugged in.
This script uses `rsync` to copy files from the Photobooth data folder to the USB drive when it is plugged in. It also creates a status file that can be used to monitor the copy progress via a web
page.
The script is triggered via a udev rule when an USB drive with NTFS filesystem is plugged in.

## Requires:

- python3
- rsync
- ntfs-3g
- udev

## Installation

`sudo touch /usr/local/bin/fotobox-copy.sh`
`sudo chmod +x /usr/local/bin/fotobox-copy.sh`
`sudo nano /usr/local/bin/fotobox-copy.sh`

```
#!/usr/bin/env bash
set -Eeuo pipefail

# Configuration

PART="${1}" # e.g. sdb1
DEV="/dev/${PART}"
SRC="/var/www/html/data"
SUB1="images"
SUB2="tmp"
STATUS="/var/www/html/private/copystatus.json"

# ---------- Status helper ----------

write_status() {
local state="$1" pct="$2" msg="$3"
local tmp="${STATUS}.tmp"

# Create JSON safely

printf '{"state":"%s","percent":%s,"message":%s}\n' \
"$state" "$pct" "$(python3 -c "import json; print(json.dumps('$msg'))")" | sudo tee "$tmp" >/dev/null

sudo mv "$tmp" "$STATUS"
}

fail() {
local code="$1"
local line="$2"
write_status "error" 0 "Error (code ${code}) in line ${line}."
exit "$code"
}

trap 'fail $? $LINENO' ERR

# ---------- Start ----------

write_status "starting" 0 "USB detected: ${DEV}. Prepare..."

# Check if device exists

if [[ ! -b "$DEV" ]]; then
write_status "error" 0 "Device ${DEV} not found."
exit 1
fi

# Mount device if not already mounted

MNT="$(lsblk -no MOUNTPOINT "$DEV" | head -n1 || true)"
if [[ -z "${MNT}" ]]; then
sudo mkdir -p /mnt/usbdrive

# mount mit 'flush' Option für exFAT hilft, Puffer schneller zu leeren

sudo mount -o flush "$DEV" /mnt/usbdrive
MNT="/mnt/usbdrive"
fi

DEST="${MNT}/images"
sudo mkdir -p "$DEST"

# Existenz der Quellordner prüfen

[[ -d "${SRC}/${SUB1}" ]] || { write_status "error" 0 "Source ${SUB1} missing"; exit 2; }
[[ -d "${SRC}/${SUB2}" ]] || { write_status "error" 0 "Source ${SUB2} missing"; exit 2; }

# ---------- Prepare Rsync ----------

# --no-inc-recursive: No jumping progress percentage
# --info=progress2: Compact progress output

RSYNC_OPTS=(
-rltD
--info=progress2
--no-inc-recursive
--no-owner --no-group --no-perms
--no-acls --no-xattrs
--omit-dir-times
--modify-window=2
)

write_status "copying" 1 "Calculate copy size..."

# Run rdync in dry-run to get total size

# stdbuf & tr to handle progress output line by line

set +e
sudo stdbuf -oL rsync "${RSYNC_OPTS[@]}" \
"${SRC}/${SUB1}" "${SRC}/${SUB2}" \
"$DEST/" 2>&1 | tr '\r' '\n' | while IFS= read -r line; do

if [[ "$line" =~ ([0-9]{1,3})% ]]; then
p="${BASH_REMATCH[1]}"

# Scale to 95% to leave room for final sync step
# Prevents user from thinking it hung at 100%
display_p=$(( p * 95 / 100 ))

# Only update every percent to reduce writes
write_status "copying" "$display_p" "Copied ${p}%..."
fi

done
RSYNC_EXIT=${PIPESTATUS[0]}
set -e

if [ "$RSYNC_EXIT" -ne 0 ] && [ "$RSYNC_EXIT" -ne 24 ]; then
fail "$RSYNC_EXIT" "rsync failed"
fi

# ---------- Final sync ----------

write_status "syncing" 96 "Finalise copy..."

# Sync to ensure all data is written to stick to avoid corruption

sync

write_status "done" 100 "Done. You can remove the USB drive now."

# Unmount only if we mounted it

if [[ "${MNT}" == "/mnt/usbdrive" ]]; then

# Give some time to ensure all writes are done

sleep 1
sudo umount /mnt/usbdrive || sudo umount -l /mnt/usbdrive
fi

exit 0

```

Setup udev rule

```
echo 'ACTION=="add", SUBSYSTEM=="block", ENV{DEVTYPE}=="partition", ENV{ID_FS_TYPE}=="ntfs", RUN+="/usr/local/bin/fotobox-copy.sh %k"' | sudo tee /etc/udev/rules.d/99-fotobox-autocopy.rules

sudo udevadm control --reload-rules
sudo udevadm trigger
```

Create status file and set permissions

```
sudo touch /var/www/html/private/copystatus.json
sudo chown root:www-data /var/www/html/private/copystatus.json
sudo chmod 664 /var/www/html/private/copystatus.json
sudo chown -R www-data:www-data /var/www/html/private
```

Create status page to monitor progress in folder `private`
`sudo -u www-data nano /var/www/html/private/status.php`

```
<?php
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Kopiervorgang…</title>
</head>
<body style="
background-image: url('/private/images/background/Copy_Background.jpg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
margin: 0;
padding: 0;
overflow-x:hidden;
overflow-y:hidden;">

<div id="wrap" style="min-height:100vh; display:flex; align-items:center; justify-content:center; padding:20px;">
<div style="background:#fff; padding:20px; border-radius:12px; width:min(520px,90vw); box-shadow:0 10px 30px rgba(0,0,0,.3);">
<div id="copyMsg" style="margin-bottom:10px;">Warte auf Status…</div>
<div style="height:18px; background:#eee; border-radius:10px; overflow:hidden;">
<div id="copyBar" style="height:100%; width:0%; background:#3b82f6;"></div>
</div>
<div id="copyPct" style="margin-top:8px; font-size:14px; opacity:.8;">0%</div>
</div>
</div>

<script>
(async function(){
const bar = document.getElementById('copyBar');
const msg = document.getElementById('copyMsg');
const pct = document.getElementById('copyPct');

// Passe ggf. den Pfad an:
const STATUS_URL = '/private/copystatus.json';
const BACK_URL = '../index.php';

async function poll(){
try {
const r = await fetch(STATUS_URL, { cache: 'no-store' });
if(!r.ok) throw new Error('Status page not reachable: ' + r.status);
const s = await r.json();

const state = (s.state || '').toLowerCase();
const p = Math.max(0, Math.min(100, Number(s.percent ?? 0)));

bar.style.width = p + '%';
msg.textContent = s.message || (state ? ('Status: ' + state) : 'Kopiere ');
pct.textContent = p + '%';

if (state === 'done') {
bar.style.width = '100%';
msg.textContent = s.message || 'Fertig.';
pct.textContent = '100%';
setTimeout(() => window.location.href = BACK_URL, 3000);
return;
}

// Optional: handle error state
if (state === 'error') {
msg.textContent = s.message || 'Copy error.';
return;
}

} catch(e) {
msg.textContent = 'No status available.';
// do not jump to status page, it will flicker otherwise
} finally {
setTimeout(poll, 400);
}
}

poll();
})();
</script>
</body>
</html>
```

Create auto-refresh script to redirect to status page when copy is running
`sudo nano /var/www/html/private/copyrun.js`

```
(async function(){
const STATUS_URL = '/private/copystatus.json';
const STATUS_PAGE = '/private/status.php';

async function check(){
try {
const r = await fetch(STATUS_URL + '?t=' + Date.now(), { cache: 'no-store' });
if (!r.ok) throw new Error('no status');
const s = await r.json();
const state = (s.state || '').toLowerCase();

if (state === 'starting' || state === 'copying') {
// >>> HIER passiert der Sprung <<<
window.location.href = STATUS_PAGE;
return;
}
} catch(e) {
// no status file or error - do nothing
}
setTimeout(check, 600);
}

check();
})();
```


3 changes: 3 additions & 0 deletions docs/scripts/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Scripts

- [Auto copy to USB (NTFS)](auto-copy-to-usb-ntfs.md)
1 change: 1 addition & 0 deletions mkdocs_remote.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ nav:
- Sounds: customizing/sounds.md
- Themes: customizing/themes.md
- CSS styles: customizing/styles.md
- Scripts: scripts/index.md
- Community:
- Chat on Telegram: https://t.me/PhotoboothGroup
- Contributor Covenant Code of Conduct: code_of_conduct.md
Expand Down
Loading