///|
pub fn collect_uris(playlist : HlsPlaylist) -> Array[UriEntry] {
let entries : Array[UriEntry] = []
for segment in playlist.segments {
entries.push({ role: SegmentUri, uri: segment.uri, line: segment.line })
match segment.key {
Some(key) =>
match key.uri {
Some(uri) => entries.push({ role: KeyUri, uri, line: key.line })
None => ()
}
None => ()
}
match segment.map {
Some(map) => entries.push({ role: MapUri, uri: map.uri, line: map.line })
None => ()
}
}
for variant in playlist.variants {
entries.push({ role: VariantUri, uri: variant.uri, line: variant.line })
}
for rendition in playlist.renditions {
match rendition.uri {
Some(uri) => entries.push({ role: RenditionUri, uri, line: rendition.line })
None => ()
}
}
entries
}
///|
pub fn audit_uris(playlist : HlsPlaylist) -> UriAudit {
let entries = collect_uris(playlist)
let mut absolute_uris = 0
let mut relative_uris = 0
let mut data_uris = 0
let mut empty_uris = 0
let mut parent_traversal_uris = 0
for entry in entries {
if entry.uri == "" {
empty_uris += 1
} else if entry.uri.has_prefix("data:") {
data_uris += 1
} else if uri_is_absolute(entry.uri) {
absolute_uris += 1
} else {
relative_uris += 1
}
if uri_has_parent_traversal(entry.uri) {
parent_traversal_uris += 1
}
}
{
total: entries.length(),
absolute_uris,
relative_uris,
data_uris,
empty_uris,
parent_traversal_uris,
entries,
}
}
///|
fn uri_is_absolute(uri : String) -> Bool {
uri.contains("://") || uri.has_prefix("//")
}
///|
fn uri_has_parent_traversal(uri : String) -> Bool {
uri == ".." || uri.has_prefix("../") || uri.contains("/../")
}
///|
pub fn uri_role_to_string(role : UriRole) -> String {
match role {
SegmentUri => "segment"
VariantUri => "variant"
RenditionUri => "rendition"
KeyUri => "key"
MapUri => "map"
}
}
///|
pub fn format_uri_audit(audit : UriAudit) -> String {
"uris=\{audit.total}, relative=\{audit.relative_uris}, absolute=\{audit.absolute_uris}, data=\{audit.data_uris}, empty=\{audit.empty_uris}, parent_traversal=\{audit.parent_traversal_uris}"
}
///|
pub fn ladder_report(playlist : HlsPlaylist) -> LadderReport {
let mut lowest = 0
let mut highest = 0
let mut monotonic = true
let mut previous = -1
let mut duplicate_bandwidths = 0
let mut has_resolution_gap = false
for i, variant in playlist.variants {
if i == 0 {
lowest = variant.bandwidth
highest = variant.bandwidth
} else {
if variant.bandwidth < lowest {
lowest = variant.bandwidth
}
if variant.bandwidth > highest {
highest = variant.bandwidth
}
}
if previous > variant.bandwidth {
monotonic = false
}
previous = variant.bandwidth
for j in 0.. String {
"variants=\{report.variants}, lowest=\{report.lowest_bandwidth}, highest=\{report.highest_bandwidth}, monotonic=\{report.monotonic}, duplicates=\{report.duplicate_bandwidths}, resolution_gap=\{report.has_resolution_gap}"
}
///|
pub fn select_variant_by_bandwidth(
playlist : HlsPlaylist,
max_bandwidth : Int,
) -> VariantStream? {
let mut best_under : VariantStream? = None
let mut lowest : VariantStream? = None
for variant in playlist.variants {
match lowest {
Some(current) =>
if variant.bandwidth < current.bandwidth {
lowest = Some(variant)
}
None => lowest = Some(variant)
}
if variant.bandwidth <= max_bandwidth {
match best_under {
Some(current) =>
if variant.bandwidth > current.bandwidth {
best_under = Some(variant)
}
None => best_under = Some(variant)
}
}
}
match best_under {
Some(value) => Some(value)
None => lowest
}
}
///|
pub fn filter_variants_by_height(
playlist : HlsPlaylist,
max_height : Int,
) -> Array[VariantStream] {
let out : Array[VariantStream] = []
for variant in playlist.variants {
match variant.resolution {
Some(resolution) =>
if resolution.height <= max_height {
out.push(variant)
}
None => ()
}
}
out
}
///|
pub fn rewrite_uri_prefix(
playlist : HlsPlaylist,
old_prefix : String,
new_prefix : String,
) -> HlsPlaylist {
let segments : Array[Segment] = []
let variants : Array[VariantStream] = []
let renditions : Array[Rendition] = []
for segment in playlist.segments {
segments.push({
..segment,
uri: rewrite_uri(segment.uri, old_prefix, new_prefix),
key: rewrite_key(segment.key, old_prefix, new_prefix),
map: rewrite_map(segment.map, old_prefix, new_prefix),
})
}
for variant in playlist.variants {
variants.push({
..variant,
uri: rewrite_uri(variant.uri, old_prefix, new_prefix),
})
}
for rendition in playlist.renditions {
renditions.push({
..rendition,
uri: rewrite_optional_uri(rendition.uri, old_prefix, new_prefix),
})
}
{ ..playlist, segments, variants, renditions }
}
///|
fn rewrite_key(
key : KeyInfo?,
old_prefix : String,
new_prefix : String,
) -> KeyInfo? {
match key {
Some(value) =>
Some({
..value,
uri: rewrite_optional_uri(value.uri, old_prefix, new_prefix),
})
None => None
}
}
///|
fn rewrite_map(
map : SegmentMap?,
old_prefix : String,
new_prefix : String,
) -> SegmentMap? {
match map {
Some(value) =>
Some({ ..value, uri: rewrite_uri(value.uri, old_prefix, new_prefix) })
None => None
}
}
///|
fn rewrite_optional_uri(
uri : String?,
old_prefix : String,
new_prefix : String,
) -> String? {
match uri {
Some(value) => Some(rewrite_uri(value, old_prefix, new_prefix))
None => None
}
}
///|
fn rewrite_uri(uri : String, old_prefix : String, new_prefix : String) -> String {
if old_prefix == "" {
new_prefix + uri
} else {
match uri.strip_prefix(old_prefix) {
Some(rest) => new_prefix + rest.to_owned()
None => uri
}
}
}