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
186 lines
4.1 KiB
Go
186 lines
4.1 KiB
Go
package i18n
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
// MessageFile represents a parsed message file.
|
|
type MessageFile struct {
|
|
Path string
|
|
Tag language.Tag
|
|
Format string
|
|
Messages []*Message
|
|
}
|
|
|
|
// ParseMessageFileBytes returns the messages parsed from file.
|
|
func ParseMessageFileBytes(buf []byte, path string, unmarshalFuncs map[string]UnmarshalFunc) (*MessageFile, error) {
|
|
lang, format := parsePath(path)
|
|
tag := language.Make(lang)
|
|
messageFile := &MessageFile{
|
|
Path: path,
|
|
Tag: tag,
|
|
Format: format,
|
|
}
|
|
if len(buf) == 0 {
|
|
return messageFile, nil
|
|
}
|
|
unmarshalFunc := unmarshalFuncs[messageFile.Format]
|
|
if unmarshalFunc == nil {
|
|
if messageFile.Format == "json" {
|
|
unmarshalFunc = json.Unmarshal
|
|
} else {
|
|
return nil, fmt.Errorf("no unmarshaler registered for %s", messageFile.Format)
|
|
}
|
|
}
|
|
var err error
|
|
var raw interface{}
|
|
if err = unmarshalFunc(buf, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
m, err := isMessage(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if messageFile.Messages, err = recGetMessages(raw, m, true); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return messageFile, nil
|
|
}
|
|
|
|
const nestedSeparator = "."
|
|
|
|
var errInvalidTranslationFile = errors.New("invalid translation file, expected key-values, got a single value")
|
|
|
|
// recGetMessages looks for translation messages inside "raw" parameter,
|
|
// scanning nested maps using recursion.
|
|
func recGetMessages(raw interface{}, isMapMessage, isInitialCall bool) ([]*Message, error) {
|
|
var messages []*Message
|
|
var err error
|
|
|
|
switch data := raw.(type) {
|
|
case string:
|
|
if isInitialCall {
|
|
return nil, errInvalidTranslationFile
|
|
}
|
|
m, err := NewMessage(data)
|
|
return []*Message{m}, err
|
|
|
|
case map[string]interface{}:
|
|
if isMapMessage {
|
|
m, err := NewMessage(data)
|
|
return []*Message{m}, err
|
|
}
|
|
messages = make([]*Message, 0, len(data))
|
|
for id, data := range data {
|
|
// recursively scan map items
|
|
messages, err = addChildMessages(id, data, messages)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
case map[interface{}]interface{}:
|
|
if isMapMessage {
|
|
m, err := NewMessage(data)
|
|
return []*Message{m}, err
|
|
}
|
|
messages = make([]*Message, 0, len(data))
|
|
for id, data := range data {
|
|
strid, ok := id.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected key to be string but got %#v", id)
|
|
}
|
|
// recursively scan map items
|
|
messages, err = addChildMessages(strid, data, messages)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
case []interface{}:
|
|
// Backward compatibility for v1 file format.
|
|
messages = make([]*Message, 0, len(data))
|
|
for _, data := range data {
|
|
// recursively scan slice items
|
|
m, err := isMessage(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
childMessages, err := recGetMessages(data, m, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
messages = append(messages, childMessages...)
|
|
}
|
|
|
|
case nil:
|
|
if isInitialCall {
|
|
return nil, errInvalidTranslationFile
|
|
}
|
|
m, err := NewMessage("")
|
|
return []*Message{m}, err
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unsupported file format %T", raw)
|
|
}
|
|
|
|
return messages, nil
|
|
}
|
|
|
|
func addChildMessages(id string, data interface{}, messages []*Message) ([]*Message, error) {
|
|
isChildMessage, err := isMessage(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
childMessages, err := recGetMessages(data, isChildMessage, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, m := range childMessages {
|
|
if isChildMessage {
|
|
if m.ID == "" {
|
|
m.ID = id // start with innermost key
|
|
}
|
|
} else {
|
|
m.ID = id + nestedSeparator + m.ID // update ID with each nested key on the way
|
|
}
|
|
messages = append(messages, m)
|
|
}
|
|
return messages, nil
|
|
}
|
|
|
|
func parsePath(path string) (langTag, format string) {
|
|
formatStartIdx := -1
|
|
for i := len(path) - 1; i >= 0; i-- {
|
|
c := path[i]
|
|
if os.IsPathSeparator(c) {
|
|
if formatStartIdx != -1 {
|
|
langTag = path[i+1 : formatStartIdx]
|
|
}
|
|
return
|
|
}
|
|
if path[i] == '.' {
|
|
if formatStartIdx != -1 {
|
|
langTag = path[i+1 : formatStartIdx]
|
|
return
|
|
}
|
|
if formatStartIdx == -1 {
|
|
format = path[i+1:]
|
|
formatStartIdx = i
|
|
}
|
|
}
|
|
}
|
|
if formatStartIdx != -1 {
|
|
langTag = path[:formatStartIdx]
|
|
}
|
|
return
|
|
}
|