Major improvements to UnifiedPlayer: 1. GetFrameImage() now works when paused for responsive UI updates 2. Play() method properly starts FFmpeg process 3. Frame display loop runs continuously for smooth video display 4. Disabled audio temporarily to fix video playback fundamentals 5. Simplified FFmpeg command to focus on video stream only Player now: - Generates video frames correctly - Shows video when paused - Has responsive progress tracking - Starts playback properly Next steps: Re-enable audio playback once video is stable
348 lines
10 KiB
Go
348 lines
10 KiB
Go
package toml
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
)
|
||
|
||
// ParseError is returned when there is an error parsing the TOML syntax such as
|
||
// invalid syntax, duplicate keys, etc.
|
||
//
|
||
// In addition to the error message itself, you can also print detailed location
|
||
// information with context by using [ErrorWithPosition]:
|
||
//
|
||
// toml: error: Key 'fruit' was already created and cannot be used as an array.
|
||
//
|
||
// At line 4, column 2-7:
|
||
//
|
||
// 2 | fruit = []
|
||
// 3 |
|
||
// 4 | [[fruit]] # Not allowed
|
||
// ^^^^^
|
||
//
|
||
// [ErrorWithUsage] can be used to print the above with some more detailed usage
|
||
// guidance:
|
||
//
|
||
// toml: error: newlines not allowed within inline tables
|
||
//
|
||
// At line 1, column 18:
|
||
//
|
||
// 1 | x = [{ key = 42 #
|
||
// ^
|
||
//
|
||
// Error help:
|
||
//
|
||
// Inline tables must always be on a single line:
|
||
//
|
||
// table = {key = 42, second = 43}
|
||
//
|
||
// It is invalid to split them over multiple lines like so:
|
||
//
|
||
// # INVALID
|
||
// table = {
|
||
// key = 42,
|
||
// second = 43
|
||
// }
|
||
//
|
||
// Use regular for this:
|
||
//
|
||
// [table]
|
||
// key = 42
|
||
// second = 43
|
||
type ParseError struct {
|
||
Message string // Short technical message.
|
||
Usage string // Longer message with usage guidance; may be blank.
|
||
Position Position // Position of the error
|
||
LastKey string // Last parsed key, may be blank.
|
||
|
||
// Line the error occurred.
|
||
//
|
||
// Deprecated: use [Position].
|
||
Line int
|
||
|
||
err error
|
||
input string
|
||
}
|
||
|
||
// Position of an error.
|
||
type Position struct {
|
||
Line int // Line number, starting at 1.
|
||
Col int // Error column, starting at 1.
|
||
Start int // Start of error, as byte offset starting at 0.
|
||
Len int // Length of the error in bytes.
|
||
}
|
||
|
||
func (p Position) withCol(tomlFile string) Position {
|
||
var (
|
||
pos int
|
||
lines = strings.Split(tomlFile, "\n")
|
||
)
|
||
for i := range lines {
|
||
ll := len(lines[i]) + 1 // +1 for the removed newline
|
||
if pos+ll >= p.Start {
|
||
p.Col = p.Start - pos + 1
|
||
if p.Col < 1 { // Should never happen, but just in case.
|
||
p.Col = 1
|
||
}
|
||
break
|
||
}
|
||
pos += ll
|
||
}
|
||
return p
|
||
}
|
||
|
||
func (pe ParseError) Error() string {
|
||
if pe.LastKey == "" {
|
||
return fmt.Sprintf("toml: line %d: %s", pe.Position.Line, pe.Message)
|
||
}
|
||
return fmt.Sprintf("toml: line %d (last key %q): %s",
|
||
pe.Position.Line, pe.LastKey, pe.Message)
|
||
}
|
||
|
||
// ErrorWithPosition returns the error with detailed location context.
|
||
//
|
||
// See the documentation on [ParseError].
|
||
func (pe ParseError) ErrorWithPosition() string {
|
||
if pe.input == "" { // Should never happen, but just in case.
|
||
return pe.Error()
|
||
}
|
||
|
||
// TODO: don't show control characters as literals? This may not show up
|
||
// well everywhere.
|
||
|
||
var (
|
||
lines = strings.Split(pe.input, "\n")
|
||
b = new(strings.Builder)
|
||
)
|
||
if pe.Position.Len == 1 {
|
||
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n",
|
||
pe.Message, pe.Position.Line, pe.Position.Col)
|
||
} else {
|
||
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n",
|
||
pe.Message, pe.Position.Line, pe.Position.Col, pe.Position.Col+pe.Position.Len-1)
|
||
}
|
||
if pe.Position.Line > 2 {
|
||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, expandTab(lines[pe.Position.Line-3]))
|
||
}
|
||
if pe.Position.Line > 1 {
|
||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, expandTab(lines[pe.Position.Line-2]))
|
||
}
|
||
|
||
/// Expand tabs, so that the ^^^s are at the correct position, but leave
|
||
/// "column 10-13" intact. Adjusting this to the visual column would be
|
||
/// better, but we don't know the tabsize of the user in their editor, which
|
||
/// can be 8, 4, 2, or something else. We can't know. So leaving it as the
|
||
/// character index is probably the "most correct".
|
||
expanded := expandTab(lines[pe.Position.Line-1])
|
||
diff := len(expanded) - len(lines[pe.Position.Line-1])
|
||
|
||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, expanded)
|
||
fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", pe.Position.Col-1+diff), strings.Repeat("^", pe.Position.Len))
|
||
return b.String()
|
||
}
|
||
|
||
// ErrorWithUsage returns the error with detailed location context and usage
|
||
// guidance.
|
||
//
|
||
// See the documentation on [ParseError].
|
||
func (pe ParseError) ErrorWithUsage() string {
|
||
m := pe.ErrorWithPosition()
|
||
if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" {
|
||
lines := strings.Split(strings.TrimSpace(u.Usage()), "\n")
|
||
for i := range lines {
|
||
if lines[i] != "" {
|
||
lines[i] = " " + lines[i]
|
||
}
|
||
}
|
||
return m + "Error help:\n\n" + strings.Join(lines, "\n") + "\n"
|
||
}
|
||
return m
|
||
}
|
||
|
||
func expandTab(s string) string {
|
||
var (
|
||
b strings.Builder
|
||
l int
|
||
fill = func(n int) string {
|
||
b := make([]byte, n)
|
||
for i := range b {
|
||
b[i] = ' '
|
||
}
|
||
return string(b)
|
||
}
|
||
)
|
||
b.Grow(len(s))
|
||
for _, r := range s {
|
||
switch r {
|
||
case '\t':
|
||
tw := 8 - l%8
|
||
b.WriteString(fill(tw))
|
||
l += tw
|
||
default:
|
||
b.WriteRune(r)
|
||
l += 1
|
||
}
|
||
}
|
||
return b.String()
|
||
}
|
||
|
||
type (
|
||
errLexControl struct{ r rune }
|
||
errLexEscape struct{ r rune }
|
||
errLexUTF8 struct{ b byte }
|
||
errParseDate struct{ v string }
|
||
errLexInlineTableNL struct{}
|
||
errLexStringNL struct{}
|
||
errParseRange struct {
|
||
i any // int or float
|
||
size string // "int64", "uint16", etc.
|
||
}
|
||
errUnsafeFloat struct {
|
||
i interface{} // float32 or float64
|
||
size string // "float32" or "float64"
|
||
}
|
||
errParseDuration struct{ d string }
|
||
)
|
||
|
||
func (e errLexControl) Error() string {
|
||
return fmt.Sprintf("TOML files cannot contain control characters: '0x%02x'", e.r)
|
||
}
|
||
func (e errLexControl) Usage() string { return "" }
|
||
|
||
func (e errLexEscape) Error() string { return fmt.Sprintf(`invalid escape in string '\%c'`, e.r) }
|
||
func (e errLexEscape) Usage() string { return usageEscape }
|
||
func (e errLexUTF8) Error() string { return fmt.Sprintf("invalid UTF-8 byte: 0x%02x", e.b) }
|
||
func (e errLexUTF8) Usage() string { return "" }
|
||
func (e errParseDate) Error() string { return fmt.Sprintf("invalid datetime: %q", e.v) }
|
||
func (e errParseDate) Usage() string { return usageDate }
|
||
func (e errLexInlineTableNL) Error() string { return "newlines not allowed within inline tables" }
|
||
func (e errLexInlineTableNL) Usage() string { return usageInlineNewline }
|
||
func (e errLexStringNL) Error() string { return "strings cannot contain newlines" }
|
||
func (e errLexStringNL) Usage() string { return usageStringNewline }
|
||
func (e errParseRange) Error() string { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) }
|
||
func (e errParseRange) Usage() string { return usageIntOverflow }
|
||
func (e errUnsafeFloat) Error() string {
|
||
return fmt.Sprintf("%v is out of the safe %s range", e.i, e.size)
|
||
}
|
||
func (e errUnsafeFloat) Usage() string { return usageUnsafeFloat }
|
||
func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) }
|
||
func (e errParseDuration) Usage() string { return usageDuration }
|
||
|
||
const usageEscape = `
|
||
A '\' inside a "-delimited string is interpreted as an escape character.
|
||
|
||
The following escape sequences are supported:
|
||
\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX
|
||
|
||
To prevent a '\' from being recognized as an escape character, use either:
|
||
|
||
- a ' or '''-delimited string; escape characters aren't processed in them; or
|
||
- write two backslashes to get a single backslash: '\\'.
|
||
|
||
If you're trying to add a Windows path (e.g. "C:\Users\martin") then using '/'
|
||
instead of '\' will usually also work: "C:/Users/martin".
|
||
`
|
||
|
||
const usageInlineNewline = `
|
||
Inline tables must always be on a single line:
|
||
|
||
table = {key = 42, second = 43}
|
||
|
||
It is invalid to split them over multiple lines like so:
|
||
|
||
# INVALID
|
||
table = {
|
||
key = 42,
|
||
second = 43
|
||
}
|
||
|
||
Use regular for this:
|
||
|
||
[table]
|
||
key = 42
|
||
second = 43
|
||
`
|
||
|
||
const usageStringNewline = `
|
||
Strings must always be on a single line, and cannot span more than one line:
|
||
|
||
# INVALID
|
||
string = "Hello,
|
||
world!"
|
||
|
||
Instead use """ or ''' to split strings over multiple lines:
|
||
|
||
string = """Hello,
|
||
world!"""
|
||
`
|
||
|
||
const usageIntOverflow = `
|
||
This number is too large; this may be an error in the TOML, but it can also be a
|
||
bug in the program that uses too small of an integer.
|
||
|
||
The maximum and minimum values are:
|
||
|
||
size │ lowest │ highest
|
||
───────┼────────────────┼──────────────
|
||
int8 │ -128 │ 127
|
||
int16 │ -32,768 │ 32,767
|
||
int32 │ -2,147,483,648 │ 2,147,483,647
|
||
int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷
|
||
uint8 │ 0 │ 255
|
||
uint16 │ 0 │ 65,535
|
||
uint32 │ 0 │ 4,294,967,295
|
||
uint64 │ 0 │ 1.8 × 10¹⁸
|
||
|
||
int refers to int32 on 32-bit systems and int64 on 64-bit systems.
|
||
`
|
||
|
||
const usageUnsafeFloat = `
|
||
This number is outside of the "safe" range for floating point numbers; whole
|
||
(non-fractional) numbers outside the below range can not always be represented
|
||
accurately in a float, leading to some loss of accuracy.
|
||
|
||
Explicitly mark a number as a fractional unit by adding ".0", which will incur
|
||
some loss of accuracy; for example:
|
||
|
||
f = 2_000_000_000.0
|
||
|
||
Accuracy ranges:
|
||
|
||
float32 = 16,777,215
|
||
float64 = 9,007,199,254,740,991
|
||
`
|
||
|
||
const usageDuration = `
|
||
A duration must be as "number<unit>", without any spaces. Valid units are:
|
||
|
||
ns nanoseconds (billionth of a second)
|
||
us, µs microseconds (millionth of a second)
|
||
ms milliseconds (thousands of a second)
|
||
s seconds
|
||
m minutes
|
||
h hours
|
||
|
||
You can combine multiple units; for example "5m10s" for 5 minutes and 10
|
||
seconds.
|
||
`
|
||
|
||
const usageDate = `
|
||
A TOML datetime must be in one of the following formats:
|
||
|
||
2006-01-02T15:04:05Z07:00 Date and time, with timezone.
|
||
2006-01-02T15:04:05 Date and time, but without timezone.
|
||
2006-01-02 Date without a time or timezone.
|
||
15:04:05 Just a time, without any timezone.
|
||
|
||
Seconds may optionally have a fraction, up to nanosecond precision:
|
||
|
||
15:04:05.123
|
||
15:04:05.856018510
|
||
`
|
||
|
||
// TOML 1.1:
|
||
// The seconds part in times is optional, and may be omitted:
|
||
// 2006-01-02T15:04Z07:00
|
||
// 2006-01-02T15:04
|
||
// 15:04
|