vsd 0.4.3

Download video streams served over HTTP from websites, DASH (.mpd) and HLS (.m3u8) playlists.
use crate::playlist;

pub(crate) fn parse_as_master(
    m3u8: &m3u8_rs::MasterPlaylist,
    uri: &str,
) -> playlist::MasterPlaylist {
    let mut streams = vec![];

    for video_stream in &m3u8.variants {
        streams.push(playlist::MediaPlaylist {
            bandwidth: Some(video_stream.bandwidth),
            channels: None,
            codecs: video_stream.codecs.to_owned(),
            extension: Some("ts".to_owned()), // Cannot be comment here
            frame_rate: video_stream.frame_rate.map(|x| x as f32),
            id: String::new(), // Cannot be comment here
            i_frame: video_stream.is_i_frame,
            language: None,
            live: false, // Cannot be comment here
            media_sequence: 0,
            media_type: playlist::MediaType::Video,
            playlist_type: playlist::PlaylistType::Hls,
            resolution: if let Some(m3u8_rs::Resolution { width, height }) = video_stream.resolution
            {
                Some((width, height))
            } else {
                None
            },
            segments: vec![], // Cannot be comment here
            uri: video_stream.uri.to_owned(),
        });
    }

    for alternative_stream in &m3u8.alternatives {
        if let Some(uri) = &alternative_stream.uri {
            match alternative_stream.media_type {
                m3u8_rs::AlternativeMediaType::Video => streams.push(playlist::MediaPlaylist {
                    bandwidth: None, // Cannot be comment here
                    channels: None,
                    codecs: None,                     // Cannot be comment here
                    extension: Some("ts".to_owned()), // Cannot be comment here
                    frame_rate: None,                 // Cannot be comment here
                    id: String::new(),                // Cannot be comment here
                    i_frame: false,                   // Cannot be comment here
                    language: None,
                    live: false, // Cannot be comment here
                    media_sequence: 0,
                    media_type: playlist::MediaType::Video,
                    playlist_type: playlist::PlaylistType::Hls,
                    resolution: None, // Cannot be comment here
                    segments: vec![], // Cannot be comment here
                    uri: uri.to_owned(),
                }),

                m3u8_rs::AlternativeMediaType::Audio => streams.push(playlist::MediaPlaylist {
                    bandwidth: None, // Cannot be comment here
                    channels: alternative_stream
                        .channels
                        .as_ref()
                        .map(|x| x.parse::<f32>().unwrap()),
                    codecs: None,                     // Cannot be comment here
                    extension: Some("ts".to_owned()), // Cannot be comment here
                    frame_rate: None,
                    id: String::new(), // Cannot be comment here
                    i_frame: false,
                    language: alternative_stream
                        .language
                        .to_owned()
                        .or(alternative_stream.assoc_language.to_owned()),
                    live: false, // Cannot be comment here
                    media_sequence: 0,
                    media_type: playlist::MediaType::Audio,
                    playlist_type: playlist::PlaylistType::Hls,
                    resolution: None,
                    segments: vec![], // Cannot be comment here
                    uri: uri.to_owned(),
                }),

                m3u8_rs::AlternativeMediaType::ClosedCaptions
                | m3u8_rs::AlternativeMediaType::Subtitles => {
                    streams.push(playlist::MediaPlaylist {
                        bandwidth: None,
                        channels: None,
                        codecs: None,                      // Cannot be comment here
                        extension: Some("vtt".to_owned()), // Cannot be comment here
                        frame_rate: None,
                        id: String::new(), // Cannot be comment here
                        i_frame: false,
                        language: alternative_stream
                            .language
                            .to_owned()
                            .or(alternative_stream.assoc_language.to_owned()),
                        live: false, // Cannot be comment here
                        media_sequence: 0,
                        media_type: playlist::MediaType::Subtitles,
                        playlist_type: playlist::PlaylistType::Hls,
                        resolution: None,
                        segments: vec![], // Cannot be comment here
                        uri: uri.to_owned(),
                    })
                }

                m3u8_rs::AlternativeMediaType::Other(_) => streams.push(playlist::MediaPlaylist {
                    bandwidth: None,
                    channels: alternative_stream
                        .channels
                        .as_ref()
                        .map(|x| x.parse::<f32>().unwrap()),
                    codecs: None,      // Cannot be comment here
                    extension: None,   // Cannot be comment here
                    frame_rate: None,  // Cannot be comment here
                    id: String::new(), // Cannot be comment here
                    i_frame: false,    // Cannot be comment here
                    language: alternative_stream
                        .language
                        .to_owned()
                        .or(alternative_stream.assoc_language.to_owned()),
                    live: false, // Cannot be comment here
                    media_sequence: 0,
                    media_type: playlist::MediaType::Undefined,
                    playlist_type: playlist::PlaylistType::Hls,
                    resolution: None, // Cannot be comment here
                    segments: vec![], // Cannot be comment here
                    uri: uri.to_owned(),
                }),
            }
        }
    }

    playlist::MasterPlaylist {
        playlist_type: playlist::PlaylistType::Hls,
        uri: uri.to_owned(),
        streams,
    }
}

pub(crate) fn push_segments(m3u8: &m3u8_rs::MediaPlaylist, stream: &mut playlist::MediaPlaylist) {
    stream.i_frame = m3u8.i_frames_only;
    stream.live = !m3u8.end_list;
    stream.media_sequence = m3u8.media_sequence;

    let mut previous_byterange_end = 0;

    for segment in &m3u8.segments {
        let map = segment.map.as_ref().map(|x| playlist::Map {
            uri: x.uri.to_owned(),
            range: x.byte_range.as_ref().map(|x| {
                let offset = x.offset.unwrap_or(0);

                let (start, end) = if offset == 0 {
                    (
                        previous_byterange_end,
                        (previous_byterange_end + x.length) - 1,
                    )
                } else {
                    (x.length, (x.length + offset) - 1)
                };

                previous_byterange_end = end;
                playlist::Range { start, end }
            }),
        });

        let range = segment.byte_range.as_ref().map(|x| {
            let offset = x.offset.unwrap_or(0);

            let (start, end) = if offset == 0 {
                (
                    previous_byterange_end,
                    (previous_byterange_end + x.length) - 1,
                )
            } else {
                (x.length, (x.length + offset) - 1)
            };

            previous_byterange_end = end;
            playlist::Range { start, end }
        });

        stream.segments.push(playlist::Segment {
            duration: segment.duration,
            key: if let Some(m3u8_rs::Key {
                iv,
                keyformat,
                method,
                uri,
                ..
            }) = &segment.key
            {
                let mut method = match method {
                    m3u8_rs::KeyMethod::AES128 => playlist::KeyMethod::Aes128,
                    m3u8_rs::KeyMethod::None => playlist::KeyMethod::None, // This should never match according to hls specifications.
                    m3u8_rs::KeyMethod::SampleAES => playlist::KeyMethod::SampleAes,
                    m3u8_rs::KeyMethod::Other(x)
                        if x == "SAMPLE-AES-CENC" || x == "SAMPLE-AES-CTR" =>
                    {
                        playlist::KeyMethod::Mp4Decrypt
                    }
                    m3u8_rs::KeyMethod::Other(x) => playlist::KeyMethod::Other(x.to_owned()),
                };

                /*
                    .mpd (with encryption) converted to .m3u8

                    #EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://302f80dd-411e-4886-bca5-bb1f8018a024:77FD1889AAF4143B085548B3C0F95B9A",KEYFORMATVERSIONS="1",KEYFORMAT="com.apple.streamingkeydelivery"
                    #EXT-X-KEY:METHOD=SAMPLE-AES-CTR,KEYFORMAT="com.microsoft.playready",KEYFORMATVERSIONS="1",URI="data:text/plain;charset=UTF-16;base64,xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AOQBmAEIAMQAxAEsAMQB0AC8ARQBtAFEANABYAEMATQBjAEoANgBnAEkAZwA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA="
                    #EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,AAAAXHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADwSEDAvgN1BHkiGvKW7H4AYoCQSEDAvgN1BHkiGvKW7H4AYoCQSEDAvgN1BHkiGvKW7H4AYoCRI88aJmwY=",KEYID=0x302F80DD411E4886BCA5BB1F8018A024,IV=0x77FD1889AAF4143B085548B3C0F95B9A,KEYFORMATVERSIONS="1",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"

                    https://dashif.org/identifiers/content_protection
                */
                if let Some(keyformat) = keyformat {
                    method = match keyformat.as_str() {
                        "com.apple.streamingkeydelivery"
                        | "com.microsoft.playready"
                        | "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" => {
                            playlist::KeyMethod::Mp4Decrypt
                        }
                        _ => method,
                    };
                }

                Some(playlist::Key {
                    default_kid: None,
                    iv: iv.clone(),
                    key_format: keyformat.clone(),
                    method,
                    uri: uri.clone(),
                })
            } else {
                None
            },
            map,
            range,
            uri: segment.uri.to_owned(),
        });
    }

    if let Some(segment) = stream.segments.first() {
        if let Some(init) = &segment.map
            && init.uri.split('?').next().unwrap().ends_with(".mp4") {
                stream.extension = Some("m4s".to_owned());
            }

        let uri = segment.uri.split('?').next().unwrap();

        if uri.ends_with(".mp4") || uri.ends_with(".m4s") {
            stream.extension = Some("m4s".to_owned());
        }
    }
}