1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use crate::ogg_vorbis_page::{IVorbisCommentHeader, OggVorbisPage};
use crate::read_ogg_vorbis_file::{read_ogg_vorbis_file, OggVorbisPacket, OggVorbisPageResult};
use std::io::{self, Error, ErrorKind};
use tokio::io::AsyncReadExt;

/// Asynchronously trims an Ogg Vorbis file by removing segments before the first header and data after the last segment.
///
/// This function reads the Ogg Vorbis file, searches for the first header (Identification, Comment, or Setup),
/// and removes all segments before this header. The trimmed file is returned as a vector of `OggVorbisPageResult`.
///
/// # Arguments
///
/// * `reader` - An asynchronous reader implementing `AsyncReadExt` and `Unpin`.
/// * `tolerate` - A boolean indicating whether to tolerate minor errors during reading.
/// * `header_search_range` - The range in bytes to search for the header.
///
/// # Returns
///
/// A result containing a vector of `OggVorbisPageResult` if successful, or an `io::Error` if an error occurs.
pub async fn trim_ogg_vorbis_file<R: AsyncReadExt + Unpin>(
    mut reader: R,
    tolerate: bool,
    header_search_range: usize,
) -> io::Result<Vec<OggVorbisPageResult>> {
    let mut result = Vec::new();
    let mut header_found = false;

    let page_results = read_ogg_vorbis_file(&mut reader, tolerate, header_search_range).await?;
    for mut page_result in page_results {
        if !header_found {
            if let Some(found_header_index) = page_result.packets.iter().position(|packet| {
                matches!(
                    packet,
                    OggVorbisPacket::Identification(_)
                        | OggVorbisPacket::Comment(_)
                        | OggVorbisPacket::Setup(_)
                )
            }) {
                header_found = true;
                if found_header_index > 0 {
                    page_result.page = page_result.page
                        .remove_page_segment(0, found_header_index)
                        .map_err(|e| Error::new(ErrorKind::Other, e))?;
                    page_result.packets = page_result.packets.split_off(found_header_index);
                    page_result.page.update_page_checksum();
                }
            } else {
                continue;
            }
        }
        result.push(page_result);
    }

    Ok(result)
}

/// Asynchronously collects all pages of an Ogg Vorbis file.
///
/// This function reads the entire Ogg Vorbis file and returns a vector of `OggVorbisPageResult` containing
/// all the pages found in the file.
///
/// # Arguments
///
/// * `reader` - An asynchronous reader implementing `AsyncReadExt` and `Unpin`.
/// * `tolerate` - A boolean indicating whether to tolerate minor errors during reading.
/// * `header_search_range` - The range in bytes to search for the header.
///
/// # Returns
///
/// A result containing a vector of `OggVorbisPageResult` if successful, or an `io::Error` if an error occurs.
pub async fn collect_ogg_vorbis_file<R: AsyncReadExt + Unpin>(
    mut reader: R,
    tolerate: bool,
    header_search_range: usize,
) -> io::Result<Vec<OggVorbisPageResult>> {
    let mut result = Vec::new();

    let page_results = read_ogg_vorbis_file(&mut reader, tolerate, header_search_range).await?;
    for page_result in page_results {
        result.push(page_result);
    }

    Ok(result)
}

/// Finds a packet of a specified type in an Ogg Vorbis file.
///
/// This function searches through the provided `ogg_vorbis_file` for a packet of the specified type
/// ("identification", "comment", or "setup") and returns its position as a tuple of page and packet indices.
///
/// # Arguments
///
/// * `ogg_vorbis_file` - A slice of `OggVorbisPageResult` representing the Ogg Vorbis file.
/// * `packet_type` - A string specifying the type of packet to find ("identification", "comment", or "setup").
///
/// # Returns
///
/// An option containing a tuple of page and packet indices if the packet is found, or `None` if not found.
pub fn find_packet_by_type(
    ogg_vorbis_file: &[OggVorbisPageResult],
    packet_type: &str,
) -> Option<(usize, usize)> {
    ogg_vorbis_file
        .iter()
        .enumerate()
        .find_map(|(page_index, page)| {
            page.packets
                .iter()
                .enumerate()
                .find_map(|(packet_index, packet)| match packet {
                    OggVorbisPacket::Identification(_) if packet_type == "identification" => {
                        Some((page_index, packet_index))
                    }
                    OggVorbisPacket::Comment(_) if packet_type == "comment" => {
                        Some((page_index, packet_index))
                    }
                    OggVorbisPacket::Setup(_) if packet_type == "setup" => {
                        Some((page_index, packet_index))
                    }
                    _ => None,
                })
        })
}

/// Updates the comments in an Ogg Vorbis file.
///
/// This function replaces the comment packet at the specified position with new comments provided
/// as a `HashMap`. The updated Ogg Vorbis file is returned as a vector of `OggVorbisPageResult`.
///
/// # Arguments
///
/// * `ogg_vorbis_file` - A vector of `OggVorbisPageResult` representing the Ogg Vorbis file.
/// * `comments_page_index` - The index of the page containing the comment packet to update.
/// * `comments_index` - The index of the comment packet within the specified page.
/// * `new_comments` - A `HashMap` containing the new comments to replace the existing ones.
///
/// # Returns
///
/// A vector of `OggVorbisPageResult` representing the updated Ogg Vorbis file.
pub fn update_ogg_vorbis_comments(
    mut ogg_vorbis_file: Vec<OggVorbisPageResult>,
    comments_page_index: usize,
    comments_index: usize,
    new_comments: std::collections::HashMap<String, Vec<String>>,
) -> Vec<OggVorbisPageResult> {
    if comments_page_index != usize::MAX {
        let comments_page = &mut ogg_vorbis_file[comments_page_index];
        if let OggVorbisPacket::Comment(ref mut comment_packet) =
            comments_page.packets[comments_index]
        {
            let new_comment_header = IVorbisCommentHeader {
                vendor: comment_packet.data.vendor.clone(),
                comments: new_comments,
            };
            let new_comment_segment = OggVorbisPage::build_comments(new_comment_header);
            comments_page.page = comments_page
                .page
                .replace_page_segment(new_comment_segment, comments_index)
                .expect("Failed to replace page segment");
        }
    }

    ogg_vorbis_file
}