diff --git a/src/video/actors.rs b/src/video/actors.rs index a461efe..c7300ef 100644 --- a/src/video/actors.rs +++ b/src/video/actors.rs @@ -55,6 +55,16 @@ pub fn playlist_file_for(playlist_dir: &str, video_path: &Path) -> PathBuf { PathBuf::from(format!("{}/{}.m3u8", playlist_dir, filename)) } +/// Sentinel path written next to a would-be playlist when ffmpeg cannot +/// transcode the source (e.g. truncated mp4 with no moov atom). Its presence +/// causes future scans to skip the file instead of re-running ffmpeg every +/// pass. Delete the `.unsupported` file to force a retry. +pub fn playlist_unsupported_sentinel(playlist_file: &Path) -> PathBuf { + let mut s = playlist_file.as_os_str().to_owned(); + s.push(".unsupported"); + PathBuf::from(s) +} + pub async fn create_playlist(video_path: &str, playlist_file: &str) -> Result { if Path::new(playlist_file).exists() { debug!("Playlist already exists: {}", playlist_file); @@ -319,7 +329,10 @@ impl Handler for VideoPlaylistManager { .filter_map(|e| e.ok()) .filter(|e| e.file_type().is_file()) .filter(is_video) - .filter(|e| !playlist_file_for(&playlist_dir_str, e.path()).exists()) + .filter(|e| { + let playlist = playlist_file_for(&playlist_dir_str, e.path()); + !playlist.exists() && !playlist_unsupported_sentinel(&playlist).exists() + }) .collect::>(); let scan_dir_name = msg.directory.clone(); @@ -393,7 +406,8 @@ impl Handler for VideoPlaylistManager { let playlist_generator = self.playlist_generator.clone(); for video_path in msg.video_paths { - if playlist_file_for(&playlist_dir_str, &video_path).exists() { + let playlist = playlist_file_for(&playlist_dir_str, &video_path); + if playlist.exists() || playlist_unsupported_sentinel(&playlist).exists() { continue; } let path_str = video_path.to_string_lossy().to_string(); @@ -683,6 +697,20 @@ impl Handler for PlaylistGenerator { }; error!("ffmpeg failed for {}: {}", video_file, detail); cleanup_partial_hls(&playlist_tmp, playlist_path.as_str(), video_stem).await; + let sentinel = playlist_unsupported_sentinel(Path::new(&playlist_file)); + if let Err(se) = tokio::fs::write(&sentinel, b"").await { + warn!( + "Failed to write playlist sentinel {}: {}", + sentinel.display(), + se + ); + } else { + info!( + "Wrote playlist sentinel {} so future scans skip {}", + sentinel.display(), + video_file + ); + } span.set_status(Status::error(detail.clone())); Err(std::io::Error::other(detail)) }