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
/// Trait describing frame rates.
pub trait FrameRate {
    const FPS: u32;

    const DROP_FRAME: bool;

    #[doc(hidden)]
    const MAX_FRAMES: u32;

    #[doc(hidden)]
    const FRAMES_PER_MINUTE: u32 = Self::FPS * 60;

    #[doc(hidden)]
    const FRAMES_PER_HOUR: u32 = Self::FRAMES_PER_MINUTE * 60;

    #[doc(hidden)]
    const DROP_FRAME_COUNT: u32 = Self::FPS / 15;

    #[doc(hidden)]
    const DISPLAY: &'static str;


    /// Given the elements of a timecode, calculate the frame offset from zero.
    #[doc(hidden)]
    fn calculate_frame_number(hour: u32, minute: u32, second: u32, frame: u32) -> Option<u32> {
        if hour > 23
            || minute > 59
            || second > 59
            || frame > Self::FPS
            || (Self::DROP_FRAME
                && second == 0
                && minute % 10 != 0
                && frame < Self::DROP_FRAME_COUNT)
        {
            return None;
        }

        let frame_number_before_drop_frames = (Self::FRAMES_PER_HOUR * hour as u32)
            + (Self::FRAMES_PER_MINUTE * minute as u32)
            + (Self::FPS * second as u32)
            + frame as u32;

        let frame_number = if Self::DROP_FRAME {
            let tens = hour * 6 + minute / 10;
            let minutes_without_tens = minute % 10;
            let drop_frame_minutes_without_tens = if minutes_without_tens > 1 {
                minutes_without_tens
            } else {
                0
            };
            let drop_frames_per_ten = Self::DROP_FRAME_COUNT * 9;
            frame_number_before_drop_frames
                - (tens * drop_frames_per_ten)
                - drop_frame_minutes_without_tens * Self::DROP_FRAME_COUNT
        } else {
            frame_number_before_drop_frames
        };

        Some(frame_number)
    }

    /// Given a frame number, calculate the fields for a time code.
    #[doc(hidden)]
    fn calculate_time_code(frame_number: u32) -> (u8, u8, u8, u8) {
        if frame_number > Self::MAX_FRAMES {
            panic!(
                "Frame rate only supports up to {:?} frames.",
                Self::MAX_FRAMES
            );
        }

        let (hour, minute, second, frame) = if Self::DROP_FRAME {
            let frames_per_drop_minute = Self::FRAMES_PER_MINUTE - Self::DROP_FRAME_COUNT;
            let frames_per_ten = Self::FRAMES_PER_MINUTE + (frames_per_drop_minute * 9);
            let frames_per_hour = frames_per_ten * 6;

            let hour = frame_number / frames_per_hour;

            let frame_number_without_hours = frame_number % frames_per_hour;
            let tens = frame_number_without_hours / frames_per_ten;
            let frame_number_without_tens = frame_number_without_hours % frames_per_ten;

            let (minute, second, frame) = if frame_number_without_tens < Self::FRAMES_PER_MINUTE {
                let minute = tens * 10;
                let frame_number_without_minutes = frame_number_without_tens;

                let second = frame_number_without_minutes / Self::FPS;

                let frame = frame_number_without_minutes % Self::FPS;

                (minute, second, frame)
            } else {
                let frame_number_without_first_minute =
                    frame_number_without_tens - Self::FRAMES_PER_MINUTE;

                let minute = 1 + (frame_number_without_first_minute / frames_per_drop_minute);

                let frame_number_without_minutes = (frame_number_without_first_minute
                    % frames_per_drop_minute)
                    + Self::DROP_FRAME_COUNT;

                let second = frame_number_without_minutes / Self::FPS;

                let frame = frame_number_without_minutes % Self::FPS;

                (tens * 10 + minute, second, frame)
            };

            (hour, minute, second, frame)
        } else {
            let total_seconds = frame_number / Self::FPS;
            let total_minutes = total_seconds / 60;

            let hour = total_minutes / 60;
            let minute = total_minutes % 60;
            let second = total_seconds % 60;
            let frame = frame_number % Self::FPS;

            (hour, minute, second, frame)
        };

        (hour as u8, minute as u8, second as u8, frame as u8)
    }
}


macro_rules! create_frame_rate {
    ($frame_rate_name:ident, $frame_rate:expr, false, $display:expr) => {
        #[derive(Clone, PartialOrd, PartialEq)]
        pub struct $frame_rate_name;

        impl FrameRate for $frame_rate_name {
            const FPS: u32 = $frame_rate;
            const DROP_FRAME: bool = false;
            const MAX_FRAMES: u32 = 86400 * Self::FPS;
            const DISPLAY: &'static str = $display;
        }
    };
    ($frame_rate_name:ident, $frame_rate:expr, $is_drop_frame:expr, $display:expr) => {
        #[derive(Clone, PartialOrd, PartialEq)]
        pub struct $frame_rate_name;

        impl FrameRate for $frame_rate_name {
            const FPS: u32 = $frame_rate;
            const DROP_FRAME: bool = $is_drop_frame;
            const MAX_FRAMES: u32 = 86400 * Self::FPS - 144 * (18 * (Self::FPS / 30));
            const DISPLAY: &'static str = $display;
        }
    };
}

create_frame_rate!(FrameRate24, 24, false, "24");
create_frame_rate!(FrameRate25, 25, false, "25");
create_frame_rate!(FrameRate30, 30, false, "30");
create_frame_rate!(FrameRate50, 50, false, "50");
create_frame_rate!(FrameRate60, 60, false, "60");
create_frame_rate!(FrameRate2398, 24, false, "23.98");
create_frame_rate!(FrameRate2997, 30, true, "29.97");
create_frame_rate!(FrameRate5994, 60, true, "59.98");