summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/time.ts
blob: 20e2ff6d007873488ce116795042271d5f5b7207 (plain) (blame)
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
// Time-related utility functions

import { DEFAULT_FPS } from "@/stores/project-store";

export type TimeCode = "MM:SS" | "HH:MM:SS" | "HH:MM:SS:CS" | "HH:MM:SS:FF";

// Helper function to format time in various formats (MM:SS, HH:MM:SS, HH:MM:SS:CS, HH:MM:SS:FF)
export const formatTimeCode = (
  timeInSeconds: number,
  format: TimeCode = "HH:MM:SS:CS",
  fps = DEFAULT_FPS
): string => {
  const hours = Math.floor(timeInSeconds / 3600);
  const minutes = Math.floor((timeInSeconds % 3600) / 60);
  const seconds = Math.floor(timeInSeconds % 60);
  const centiseconds = Math.floor((timeInSeconds % 1) * 100);
  const frames = Math.floor((timeInSeconds % 1) * fps);

  switch (format) {
    case "MM:SS":
      return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
    case "HH:MM:SS":
      return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
    case "HH:MM:SS:CS":
      return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}:${centiseconds.toString().padStart(2, "0")}`;
    case "HH:MM:SS:FF":
      return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}:${frames.toString().padStart(2, "0")}`;
  }
};

export const parseTimeCode = (
  timeCode: string,
  format: TimeCode = "HH:MM:SS:CS",
  fps = DEFAULT_FPS
): number | null => {
  if (!timeCode || typeof timeCode !== "string") return null;

  // Remove any extra whitespace
  const cleanTimeCode = timeCode.trim();

  try {
    switch (format) {
      case "MM:SS": {
        const parts = cleanTimeCode.split(":");
        if (parts.length !== 2) return null;
        const [minutes, seconds] = parts.map((part) => parseInt(part, 10));
        if (isNaN(minutes) || isNaN(seconds)) return null;
        if (minutes < 0 || seconds < 0 || seconds >= 60) return null;
        return minutes * 60 + seconds;
      }

      case "HH:MM:SS": {
        const parts = cleanTimeCode.split(":");
        if (parts.length !== 3) return null;
        const [hours, minutes, seconds] = parts.map((part) =>
          parseInt(part, 10)
        );
        if (isNaN(hours) || isNaN(minutes) || isNaN(seconds)) return null;
        if (
          hours < 0 ||
          minutes < 0 ||
          seconds < 0 ||
          minutes >= 60 ||
          seconds >= 60
        )
          return null;
        return hours * 3600 + minutes * 60 + seconds;
      }

      case "HH:MM:SS:CS": {
        const parts = cleanTimeCode.split(":");
        if (parts.length !== 4) return null;
        const [hours, minutes, seconds, centiseconds] = parts.map((part) =>
          parseInt(part, 10)
        );
        if (
          isNaN(hours) ||
          isNaN(minutes) ||
          isNaN(seconds) ||
          isNaN(centiseconds)
        )
          return null;
        if (
          hours < 0 ||
          minutes < 0 ||
          seconds < 0 ||
          centiseconds < 0 ||
          minutes >= 60 ||
          seconds >= 60 ||
          centiseconds >= 100
        )
          return null;
        return hours * 3600 + minutes * 60 + seconds + centiseconds / 100;
      }

      case "HH:MM:SS:FF": {
        const parts = cleanTimeCode.split(":");
        if (parts.length !== 4) return null;
        const [hours, minutes, seconds, frames] = parts.map((part) =>
          parseInt(part, 10)
        );
        if (isNaN(hours) || isNaN(minutes) || isNaN(seconds) || isNaN(frames))
          return null;
        if (
          hours < 0 ||
          minutes < 0 ||
          seconds < 0 ||
          frames < 0 ||
          minutes >= 60 ||
          seconds >= 60 ||
          frames >= fps
        )
          return null;
        return hours * 3600 + minutes * 60 + seconds + frames / fps;
      }
    }
  } catch {
    return null;
  }

  return null;
};

export const guessTimeCodeFormat = (timeCode: string): TimeCode | null => {
  if (!timeCode || typeof timeCode !== "string") return null;

  const numbers = timeCode.split(":");

  if (!numbers.every((n) => !isNaN(Number(n)))) return null;

  if (numbers.length === 2) return "MM:SS";
  if (numbers.length === 3) return "HH:MM:SS";
  // todo: how to tell frames apart from cs?
  if (numbers.length === 4) return "HH:MM:SS:FF";

  return null;
};