Skip to content

Commit 1b8f604

Browse files
committed
fix(media): accept indexed HLS init files
1 parent 6139f48 commit 1b8f604

1 file changed

Lines changed: 81 additions & 39 deletions

File tree

services/rend-api/src/media.rs

Lines changed: 81 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,26 +1123,47 @@ async fn run_hls_command_with_streaming_uploads(
11231123
}
11241124

11251125
async fn normalize_hls_init_fragments(hls_dir: &Path, renditions: &[HlsRendition]) -> Result<()> {
1126-
for rendition in renditions {
1126+
for (variant_index, rendition) in renditions.iter().enumerate() {
11271127
let variant_dir = hls_dir.join(rendition.name);
11281128
let ffmpeg_init_path = variant_dir.join(HLS_FFMPEG_INIT_FILENAME);
1129+
let indexed_init_name = format!("init_{variant_index}.mp4");
1130+
let indexed_init_path = variant_dir.join(&indexed_init_name);
11291131
let normalized_init_name = format!("init_{}.mp4", rendition.name);
11301132
let normalized_init_path = variant_dir.join(&normalized_init_name);
1131-
let ffmpeg_init_exists = fs::try_exists(&ffmpeg_init_path).await?;
1132-
let normalized_init_exists = fs::try_exists(&normalized_init_path).await?;
1133+
1134+
// FFmpeg adds the numeric var_stream_map index even when the configured
1135+
// init filename does not contain `%v`. Single-variant builds may still
1136+
// emit the configured `init.mp4`, while retried normalization tests can
1137+
// already contain Rend's rendition-named form. Accept exactly one of
1138+
// those valid shapes and normalize it before uploading.
1139+
let candidate_paths = [
1140+
ffmpeg_init_path,
1141+
indexed_init_path,
1142+
normalized_init_path.clone(),
1143+
];
1144+
let mut existing_paths = Vec::new();
1145+
for candidate_path in candidate_paths {
1146+
if fs::try_exists(&candidate_path).await? {
1147+
existing_paths.push(candidate_path);
1148+
}
1149+
}
11331150
anyhow::ensure!(
1134-
ffmpeg_init_exists ^ normalized_init_exists,
1135-
"ffmpeg created an invalid {} HLS init file set",
1136-
rendition.name
1151+
existing_paths.len() == 1,
1152+
"ffmpeg created an invalid {} HLS init file set; expected exactly one of {}, {}, or {}",
1153+
rendition.name,
1154+
HLS_FFMPEG_INIT_FILENAME,
1155+
indexed_init_name,
1156+
normalized_init_name,
11371157
);
1138-
if ffmpeg_init_exists {
1139-
fs::rename(&ffmpeg_init_path, &normalized_init_path)
1158+
let source_init_path = existing_paths.pop().expect("one init path was verified");
1159+
if source_init_path != normalized_init_path {
1160+
fs::rename(&source_init_path, &normalized_init_path)
11401161
.await
11411162
.with_context(|| {
11421163
format!(
11431164
"failed to normalize {} HLS init file {}",
11441165
rendition.name,
1145-
ffmpeg_init_path.display()
1166+
source_init_path.display()
11461167
)
11471168
})?;
11481169
}
@@ -1151,7 +1172,8 @@ async fn normalize_hls_init_fragments(hls_dir: &Path, renditions: &[HlsRendition
11511172
let playlist = fs::read_to_string(&playlist_path)
11521173
.await
11531174
.with_context(|| format!("failed to read {} HLS playlist", rendition.name))?;
1154-
let normalized = normalize_hls_variant_playlist_init(&playlist, rendition.name)?;
1175+
let normalized =
1176+
normalize_hls_variant_playlist_init(&playlist, rendition.name, variant_index)?;
11551177
if normalized != playlist {
11561178
fs::write(&playlist_path, normalized)
11571179
.await
@@ -1161,8 +1183,13 @@ async fn normalize_hls_init_fragments(hls_dir: &Path, renditions: &[HlsRendition
11611183
Ok(())
11621184
}
11631185

1164-
fn normalize_hls_variant_playlist_init(playlist: &str, rendition: &str) -> Result<String> {
1186+
fn normalize_hls_variant_playlist_init(
1187+
playlist: &str,
1188+
rendition: &str,
1189+
variant_index: usize,
1190+
) -> Result<String> {
11651191
let ffmpeg_reference = format!("URI=\"{HLS_FFMPEG_INIT_FILENAME}\"");
1192+
let indexed_reference = format!("URI=\"init_{variant_index}.mp4\"");
11661193
let normalized_reference = format!("URI=\"init_{rendition}.mp4\"");
11671194
let init_maps = playlist
11681195
.lines()
@@ -1174,11 +1201,15 @@ fn normalize_hls_variant_playlist_init(playlist: &str, rendition: &str) -> Resul
11741201
);
11751202
anyhow::ensure!(
11761203
init_maps.iter().all(|line| {
1177-
line.contains(&ffmpeg_reference) || line.contains(&normalized_reference)
1204+
line.contains(&ffmpeg_reference)
1205+
|| line.contains(&indexed_reference)
1206+
|| line.contains(&normalized_reference)
11781207
}),
11791208
"{rendition} HLS playlist referenced an unexpected init file"
11801209
);
1181-
Ok(playlist.replace(&ffmpeg_reference, &normalized_reference))
1210+
Ok(playlist
1211+
.replace(&ffmpeg_reference, &normalized_reference)
1212+
.replace(&indexed_reference, &normalized_reference))
11821213
}
11831214

11841215
async fn upload_finalized_hls_fragments(
@@ -2976,7 +3007,7 @@ segment_00000.m4s
29763007
#EXT-X-ENDLIST
29773008
"#;
29783009

2979-
let normalized = normalize_hls_variant_playlist_init(playlist, "360p").unwrap();
3010+
let normalized = normalize_hls_variant_playlist_init(playlist, "360p", 0).unwrap();
29803011

29813012
assert!(normalized.contains("#EXT-X-MAP:URI=\"init_360p.mp4\""));
29823013
assert!(!normalized.contains("URI=\"init.mp4\""));
@@ -2990,13 +3021,13 @@ segment_00000.m4s
29903021
segment_00000.m4s
29913022
"#;
29923023

2993-
let error = normalize_hls_variant_playlist_init(playlist, "360p").unwrap_err();
3024+
let error = normalize_hls_variant_playlist_init(playlist, "360p", 0).unwrap_err();
29943025

29953026
assert!(error.to_string().contains("unexpected init file"));
29963027
}
29973028

29983029
#[tokio::test]
2999-
async fn hls_init_fragment_normalization_renames_file_and_playlist_reference() {
3030+
async fn hls_init_fragment_normalization_renames_indexed_ffmpeg_output() {
30003031
let unique = SystemTime::now()
30013032
.duration_since(UNIX_EPOCH)
30023033
.unwrap()
@@ -3005,36 +3036,47 @@ segment_00000.m4s
30053036
"rend-hls-init-normalization-{}-{unique}",
30063037
std::process::id()
30073038
));
3008-
let variant_dir = root.join("360p");
3009-
fs::create_dir_all(&variant_dir).await.unwrap();
3010-
fs::write(variant_dir.join(HLS_FFMPEG_INIT_FILENAME), b"init")
3039+
let renditions = &HLS_RENDITIONS[..4];
3040+
for (variant_index, rendition) in renditions.iter().enumerate() {
3041+
let variant_dir = root.join(rendition.name);
3042+
fs::create_dir_all(&variant_dir).await.unwrap();
3043+
fs::write(
3044+
variant_dir.join(format!("init_{variant_index}.mp4")),
3045+
b"init",
3046+
)
30113047
.await
30123048
.unwrap();
3013-
fs::write(
3014-
variant_dir.join("index.m3u8"),
3015-
b"#EXTM3U\n#EXT-X-MAP:URI=\"init.mp4\"\nsegment_00000.m4s\n",
3016-
)
3017-
.await
3018-
.unwrap();
3019-
3020-
normalize_hls_init_fragments(&root, &[HLS_RENDITIONS[0]])
3049+
fs::write(
3050+
variant_dir.join("index.m3u8"),
3051+
format!(
3052+
"#EXTM3U\n#EXT-X-MAP:URI=\"init_{variant_index}.mp4\"\nsegment_00000.m4s\n"
3053+
),
3054+
)
30213055
.await
30223056
.unwrap();
3057+
}
30233058

3024-
assert!(
3025-
!fs::try_exists(variant_dir.join(HLS_FFMPEG_INIT_FILENAME))
3026-
.await
3027-
.unwrap()
3028-
);
3029-
assert!(
3030-
fs::try_exists(variant_dir.join("init_360p.mp4"))
3031-
.await
3032-
.unwrap()
3033-
);
3034-
let playlist = fs::read_to_string(variant_dir.join("index.m3u8"))
3059+
normalize_hls_init_fragments(&root, renditions)
30353060
.await
30363061
.unwrap();
3037-
assert!(playlist.contains("#EXT-X-MAP:URI=\"init_360p.mp4\""));
3062+
3063+
for (variant_index, rendition) in renditions.iter().enumerate() {
3064+
let variant_dir = root.join(rendition.name);
3065+
assert!(
3066+
!fs::try_exists(variant_dir.join(format!("init_{variant_index}.mp4")))
3067+
.await
3068+
.unwrap()
3069+
);
3070+
assert!(
3071+
fs::try_exists(variant_dir.join(format!("init_{}.mp4", rendition.name)))
3072+
.await
3073+
.unwrap()
3074+
);
3075+
let playlist = fs::read_to_string(variant_dir.join("index.m3u8"))
3076+
.await
3077+
.unwrap();
3078+
assert!(playlist.contains(&format!("#EXT-X-MAP:URI=\"init_{}.mp4\"", rendition.name)));
3079+
}
30383080

30393081
fs::remove_dir_all(root).await.unwrap();
30403082
}

0 commit comments

Comments
 (0)