mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-23 23:18:06 +02:00
Enregistrements : droits du dossier compatibles Plex (PUID/PGID), code 243 expliqué (#34)
Sous Unraid, l'entrypoint faisait chmod -R 770 sur le dossier d'enregistrement : Plex (nobody:users) ne lisait plus les vidéos. Rendre la lecture à Plex retirait l'écriture au conteneur, et le match suivant a échoué sur « Erreur FFmpeg code 243 » (EACCES), sans log puisque le .log ne pouvait pas non plus être créé. - entrypoint : PUID/PGID (Unraid 99/100) et UMASK (002) ; les droits du dossier sont ajoutés, plus jamais retirés ; avertissement au démarrage si le dossier reste non inscriptible. - Serveur : vérification d'écriture avant de lancer FFmpeg, motif clair. - Codes de sortie FFmpeg courants traduits (243, 8, 228…). - Dialogue des logs : affiche le message d'erreur du serveur. Claude-Session: https://claude.ai/code/session_018RD1okqQzNYjujGirBHXpu Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
0f5b087eb7
commit
2f8f09e12f
8 files changed
+124
-11
No files matched your search
@@ -101,6 +101,28 @@ pub fn decide_post_exit_action(
|
||||
}
|
||||
}
|
||||
|
||||
/// Cause lisible d'un code de sortie FFmpeg, pour le motif d'échec affiché.
|
||||
///
|
||||
/// FFmpeg ≥ 7 sort avec son code d'erreur interne (`AVERROR`, négatif)
|
||||
/// tronqué à un octet : 243 = −13 = `EACCES`. Seules les causes que
|
||||
/// l'utilisateur peut corriger sont nommées.
|
||||
pub fn describe_ffmpeg_exit(exit_code: i32) -> Option<&'static str> {
|
||||
match exit_code {
|
||||
// −13 EACCES
|
||||
243 => Some("permission refusée sur le dossier d'enregistrement"),
|
||||
// −2 ENOENT
|
||||
254 => Some("fichier ou dossier introuvable"),
|
||||
// −28 ENOSPC
|
||||
228 => Some("disque plein"),
|
||||
// −30 EROFS
|
||||
226 => Some("dossier d'enregistrement en lecture seule"),
|
||||
// Erreurs HTTP du fournisseur (AVERROR_HTTP_*)
|
||||
8 => Some("flux refusé par le fournisseur (erreur HTTP)"),
|
||||
c if c < 0 => Some("FFmpeg interrompu par un signal"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Durée de capture (`-t`) : le temps restant jusqu'à la fin programmée,
|
||||
/// et non la durée théorique du programme — un démarrage tardif (ou une
|
||||
/// relance après coupure) ne doit pas décaler la fin de l'enregistrement.
|
||||
@@ -216,6 +238,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn causes_de_sortie_ffmpeg() {
|
||||
assert_eq!(
|
||||
describe_ffmpeg_exit(243),
|
||||
Some("permission refusée sur le dossier d'enregistrement")
|
||||
);
|
||||
assert_eq!(
|
||||
describe_ffmpeg_exit(8),
|
||||
Some("flux refusé par le fournisseur (erreur HTTP)")
|
||||
);
|
||||
assert_eq!(
|
||||
describe_ffmpeg_exit(-9),
|
||||
Some("FFmpeg interrompu par un signal")
|
||||
);
|
||||
assert_eq!(describe_ffmpeg_exit(1), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duree_de_capture() {
|
||||
let n = now();
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::db::AppDatabase;
|
||||
use crate::models::Recording;
|
||||
use crate::services::recording_decision::{
|
||||
OrphanAction, PostExitAction, RecordingAction, capture_duration, decide_orphan_action,
|
||||
decide_post_exit_action, decide_recording_action, ffmpeg_retry_delay,
|
||||
decide_post_exit_action, decide_recording_action, describe_ffmpeg_exit, ffmpeg_retry_delay,
|
||||
};
|
||||
use crate::util::b64;
|
||||
use crate::util::log_redactor::redact_url;
|
||||
@@ -631,6 +631,27 @@ impl RecordingScheduler {
|
||||
}
|
||||
}
|
||||
|
||||
// Un dossier non inscriptible (droits modifiés côté hôte) ferait
|
||||
// échouer chaque relance de FFmpeg pendant toute la fenêtre, sans
|
||||
// même de log : on le signale tout de suite.
|
||||
if let Err(e) = check_writable(&dir) {
|
||||
println!(
|
||||
"[RecordingScheduler] Dossier {} non accessible en écriture ({e}) : \"{}\" refusé",
|
||||
dir.display(),
|
||||
recording.title
|
||||
);
|
||||
self.db.update_recording_status(
|
||||
&recording.id,
|
||||
"failed",
|
||||
None,
|
||||
Some(&format!(
|
||||
"Dossier d'enregistrement non accessible en écriture ({e}). Vérifier les droits du dossier hôte (PUID/PGID)."
|
||||
)),
|
||||
false,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let file_path = dir
|
||||
.join(file_name_for(&recording))
|
||||
.to_string_lossy()
|
||||
@@ -849,8 +870,11 @@ impl RecordingScheduler {
|
||||
state.consecutive_failures
|
||||
};
|
||||
let delay = ffmpeg_retry_delay(failures);
|
||||
let cause = describe_ffmpeg_exit(exit_code)
|
||||
.map(|c| format!(", {c}"))
|
||||
.unwrap_or_default();
|
||||
let message = format!(
|
||||
"FFmpeg s'est arrêté (code {exit_code}) après {}s, avant la fin prévue — relance dans {}s (échec consécutif n°{failures})",
|
||||
"FFmpeg s'est arrêté (code {exit_code}{cause}) après {}s, avant la fin prévue — relance dans {}s (échec consécutif n°{failures})",
|
||||
lasted.as_secs(),
|
||||
delay.as_secs(),
|
||||
);
|
||||
@@ -893,7 +917,12 @@ impl RecordingScheduler {
|
||||
self.finalize(
|
||||
&active,
|
||||
"failed",
|
||||
Some(&format!("Erreur FFmpeg code {exit_code}. Voir logs.")),
|
||||
Some(&match describe_ffmpeg_exit(exit_code) {
|
||||
Some(cause) => {
|
||||
format!("Erreur FFmpeg code {exit_code} : {cause}. Voir logs.")
|
||||
}
|
||||
None => format!("Erreur FFmpeg code {exit_code}. Voir logs."),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -1234,6 +1263,14 @@ fn file_has_data(path: Option<&str>) -> bool {
|
||||
.is_some_and(|m| m.is_file() && m.len() > 0)
|
||||
}
|
||||
|
||||
/// Vérifie qu'un fichier peut être créé dans [dir] (fichier sonde supprimé).
|
||||
fn check_writable(dir: &Path) -> std::io::Result<()> {
|
||||
let probe = dir.join(format!(".xtremflow-write-test-{}", std::process::id()));
|
||||
std::fs::File::create(&probe)?;
|
||||
let _ = std::fs::remove_file(&probe);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_quietly(path: &str) {
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user