// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
// Two frames of a test pattern, as ffmpeg wrote them:
//
// ffmpeg -f lavfi -i testsrc2=size=32x32:rate=4 -t 0.5 -c:v libx264 \
// -preset ultrafast -g 2 -pix_fmt yuv420p -b:v 20k -movflags +faststart one.mp4
//
// A real caller reads a file or a socket; this one carries the file with it so
// that the example runs anywhere.
///|
/// The movie to package.
let movie : String =
#|AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAMqbW9vdgAAAGxtdmhkAAAAAAAA
#|AAAAAAAAAAAD6AAAAfQAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAA
#|AAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAlR0cmFrAAAAXHRr
#|aGQAAAADAAAAAAAAAAAAAAABAAAAAAAAAfQAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAA
#|AAAAAAABAAAAAAAAAAAAAAAAAABAAAAAACAAAAAgAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAA
#|AAEAAAH0AAAAAAABAAAAAAHMbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAABAAAAAIABVxAAA
#|AAAALWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABWaWRlb0hhbmRsZXIAAAABd21pbmYA
#|AAAUdm1oZAAAAAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAA
#|AQAAATdzdGJsAAAAt3N0c2QAAAAAAAAAAQAAAKdhdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAA
#|AAAAACAAIABIAAAASAAAAAAAAAABFUxhdmM2Mi4yOC4xMDEgbGlieDI2NAAAAAAAAAAAAAAA
#|GP//AAAALWF2Y0MBQsAK/+EAFmdCwAraJbARAAADAAEAAAMACA8SJqABAARozjyAAAAAEHBh
#|c3AAAAABAAAAAQAAABRidHJ0AAAAAAAAg5AAAIOQAAAAGHN0dHMAAAAAAAAAAQAAAAIAABAA
#|AAAAFHN0c3MAAAAAAAAAAQAAAAEAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0
#|c3oAAAAAAAAAAAAAAAIAAAegAAAAmQAAABRzdGNvAAAAAAAAAAEAAANaAAAAYnVkdGEAAABa
#|bWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWp
#|dG9vAAAAHWRhdGEAAAABAAAAAExhdmY2Mi4xMi4xMDEAAAAIZnJlZQAACEFtZGF0AAACXwYF
#|//9b3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE2NSByMzIyMyAwNDgwY2IwIC0gSC4y
#|NjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAyNSAtIGh0dHA6Ly93d3cu
#|dmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTAgcmVmPTEgZGVibG9j
#|az0wOjA6MCBhbmFseXNlPTA6MCBtZT1kaWEgc3VibWU9MCBwc3k9MSBwc3lfcmQ9MS4wMDow
#|LjAwIG1peGVkX3JlZj0wIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MCA4eDhk
#|Y3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNl
#|dD0wIHRocmVhZHM9MSBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9
#|MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRf
#|aW50cmE9MCBiZnJhbWVzPTAgd2VpZ2h0cD0wIGtleWludD0yIGtleWludF9taW49MSBzY2Vu
#|ZWN1dD0wIGludHJhX3JlZnJlc2g9MCByYz1hYnIgbWJ0cmVlPTAgYml0cmF0ZT0yMCByYXRl
#|dG9sPTEuMCBxY29tcD0wLjYwIHFwbWluPTAgcXBtYXg9NjkgcXBzdGVwPTQgaXBfcmF0aW89
#|MS40MCBhcT0wAIAAAAU5ZYiEBmgxgAIAAIEYAAgLcAAIFAAMAJwVj6BAA9+S69A3gAXwABQT
#|AEACAIGFy4Hxs7G2oQAAQLwABCyMiAACBeAAIWTAAcAAQIAABAsAt8ALgStBcFLkZ2APpdf/
#|hwABCPAAEdYyOAAIdIAAlxGdAABAXAAEBwFNAHQSLBFOGsmqYSyI//aAACAYAAIBgWqAuFaQ
#|3FTE7ogvt7/8OAAIHIAAhnMUAAYAYAKF1QShLJhkIoMnq8OAAIHIAAhnMASDAAEIUBAB8OIZ
#|NXtZZARJeJgfqAAIAMAFiyoJQlEwykUGX1eoAGAYAw6SA+B+7EpvDV6vSAACEiAHAAEBAA4E
#|ixqJoGEi375IAAIOoBgACwFhekY3cFxuv3wIAARpgABHIAAEggAARDwgABAyAAET8AAQrAAB
#|FaAAcAG4cDQXNwqmUWpyOKgFgJnDABCtDWg/4u/SuADcvqXAAAgLgACAMBQXWqdKSYCWPDsU
#|1jQQAAgdeoATY8ApEGkDJXtP4AC8ADD+hgBon8ZaCa1ge4AEAUEGhQ1G66hrHQ12BvHHgAAQ
#|GgABAVCxZEXOZSEAYK2CHNBQ4ADAAEG0oaBrMjhBTPFeH1wJoXwAcAAQUwA4AAgJgAyBSewD
#|yKYMo/gAIAQR5ABPAIIAAl/jgACGMAAJPZkBYAIAQEAAwUDsA4i6bauAABAlAAEDIDiAAJgZ
#|Hk0jB3ObSNh9NCAAAiFgYAAQdABQ3yCAACDYAAIt5kcAAQFAABBnMkAACIWAAIWAAAgOSwAk
#|AAQEgABAWBLIBUOxXfDNoIqYNjK+0AACAAADAMAAkKGoKQUjkKYUgEP1eoAAQAOACxwH5SQy
#|0dwBQC36vYAACGCAA4AAgbAAkGhobuxzAJ42hqNJ6AAAQQAABBfAAEBwACRGHQEsNQW6rAsA
#|BgA4AKHQflJgLR2AFAQvV6gAGAEAGCYjfR7IhaD8Cd6vQAACCCAAIL4AAgOAATEBaAlhqEKq
#|2AAAhDgBAABAJAKBo+qWkigRhtDU+HggABAoAAEk4AAQHgABRgCAAEBAAAQgRwBHQCIAgCQg
#|DjvUoK5EKgOlNuAYSRLqICQmyUbuX4MxY4AAEFAAAQGAGgNCt0mdGoOHSxCwlPgAAQPwABAC
#|BgFT0TxOakEPtXg3TxwABgg0ZNjHHVYXgkMqNjd+AABADDgBIZQAw6kjSbcAMW5rAwcAACBO
#|AAIAYMBEgJ7+SkBTImyaWjgAAQPAABAdAeBoyOOehKUmehRrJAWcHAAEboAASWgABFiAAFWF
#|MywABCRAAEdxiDgACEiAAI7jBYAAhIAACO6wWAAIOoAAjCMDgACEgAAI7rA4AAg6gACMIxBw
#|ABCRAAEdxiDgACEiAAI7jA4AAhIAACO6wOAAIOoAAjCMDgACEgAAI7rA4AAg6gACMIwIAAIM
#|IAAjORAABC4AAElGQAATAAEBAAAQQbhBAAEHgBrgcAAmAAICAAAgg3A5AAEHgBrhAAAgbAAC
#|DSAAIRFwgQABC5AAEBu4HAAEDYAAQaQABCIuBxAAELkAAQG7gWcwAAQmQABSrAAEEEAAT90w
#|QAAQwAABJrY8cAAQwAABJrY+WAAIQ4AAjdMQcAAQhwABG6YHAAEMAAASa2I4AAhgAACTWxg4
#|AAhDgACN0xBwABCHAAEbpgQAAQFgABJiiAACBIAAI7cggACF0AAIWlwiAAITQAAg73A5AAEL
#|oAAQtLgeAAITQAAg73CABAAEEkAAQO7hAABYAAQawABCIuBwBAAEEkAAQO7gcAAsAAINYAAh
#|EXIAAACVQZogGK8PwOAgECGQB4ACADA4dYBieCyfg21fgCKJBgqza1BcDFY0OMmDYbag/8aD
#|PwgABDJAAEK0ABYHQgABAMABO4AByFYQaQAAIM7TfFSQAAgzgAP7QfCAAEBghgABAYAwEDIX
#|SFRqfDApBgAOAAICQAAgKgBAFh6zYjEY1B9FDojGIPgDcChPRnc7x5tGuXethkg=
///|
/// Cut a movie into segments a player can fetch, and write the playlists that
/// name them.
///
/// moon run src/examples/03-package
#coverage.skip
fn main raise {
let bytes = @base64.decode(movie, ignore_whitespace=true)
let file = @mp4.Mp4::parse(bytes[:])
let tracks = file.tracks()
println("tracks: " + tracks.length().to_string())
for track in tracks {
println(
" " +
track.codec +
" " +
track.width.to_string() +
"x" +
track.height.to_string() +
", " +
track.samples.length().to_string() +
" samples",
)
}
// One segment a second is what a player expects to be able to fetch whole.
let packager = @moonmedia.Packager::new(file, target_duration=1.0)
let init = packager.init_segment()
println("initialisation: " + init.length().to_string() + " bytes")
let segments = packager.segments()
println("segments: " + segments.length().to_string())
for i, segment in segments {
println(
" segment" +
i.to_string() +
".m4s " +
segment.duration.to_string() +
" s, " +
segment.data.length().to_string() +
" bytes, starts at " +
segment.start.to_string() +
" s",
)
}
let playlist = packager.media_playlist()
println("media playlist")
println(playlist.encode())
let master = packager.master_playlist()
println("master playlist")
println(master.encode())
// The segments have to be fragments a player can fetch on their own: each
// starts with its own movie fragment header.
for segment in segments {
let fragment = @mp4.Mp4::parse(segment.data[:])
assert_eq(fragment.boxes[0].type_, "moof")
assert_eq(fragment.boxes[1].type_, "mdat")
}
println("every segment is a fragment")
}