txt

package
v0.0.0-...-95630b3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2023 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package txt implements parsing and writing the UltraStar TXT file format. The implementation aims to be compatible with UltraStar and other compatible singing games.

Unfortunately a formal specification of the UltraStar TXT format does not exist. The parser implementation of UltraStar has some weird edge cases that were intentionally left out in this package. The parser in this package should be able to parse most songs that are found in the wild.

This package does not concern itself with the encoding of songs data. It expects all input to be UTF-8 encoded strings (or io.Reader reading UTF-8 bytes). However, many UltraStar songs found in the wild are actually in different encodings such as CP-1252. These are mostly compatible with UTF-8, but may produce wrong special characters. It is your responsibility to detect the encoding of the source data and convert it appropriately.

There are some songs that make use of a special ENCODING tag. This package explicitly does not support this tag. If you want to process the tag you can either do some pre-processing or re-encode strings after they have been parsed.

There are UltraStar TXTs known that use a UTF-8 byte order mark (BOM). The parser in this package is able to understand UTF-8 and UTF-16 BOMs with no further configuration.

Index

Constants

View Source
const (
	// TagRelative is an indicator whether a song's music must be interpreted in relative mode.
	// If this tag is absent or not set to `"YES"` the song is interpreted in absolute mode.
	TagRelative = "RELATIVE"

	// TagEncoding is a known legacy tag that specifies the encoding of a txt file.
	// UltraStar and Vocaluxe only understand the values "CP1250" and "CP1252".
	// New songs should only use UTF-8 encoding.
	TagEncoding = "ENCODING"

	// TagMP3 references the audio file for a song.
	// The value is a file path relative to the TXT file.
	// The audio file may be in MP3 format but other formats are supported as well.
	// Specifically video files may also be used.
	//
	// This tag is not required but a without this tag a song has no audio.
	TagMP3 = "MP3"

	// TagVideo references the video file for a song.
	// The value is a file path relative to the TXT file.
	TagVideo = "VIDEO"

	// TagCover references the artwork file for a song.
	// The value is a file path relative to the TXT file.
	TagCover = "COVER"

	// TagBackground references the background image for a song.
	// The value is a file path relative to the TXT file.
	TagBackground = "BACKGROUND"

	// TagBPM identifies the starting BPM for a song.
	// In most cases this BPM value holds for the entire duration of a song but
	// Multi BPM songs are supported by UltraStar.
	// The actual BPM value is 4 times as high as the value stored in the TXT file.
	//
	// The value is a floating point number.
	TagBPM = "BPM"

	// TagGap identifies the number of milliseconds before beat 0 starts.
	// This is used as an offset for the entire lyrics.
	//
	// The value is a floating point number.
	TagGap = "GAP"

	// TagVideoGap identifies the number of seconds before the video starts.
	// In contrast to TagGap this is specified in seconds instead of milliseconds.
	//
	// The value is a floating point number.
	TagVideoGap = "VIDEOGAP"

	// TagNotesGap identifies an offset added to the beats of the Music.
	// This is used together with TagResolution.
	//
	// The value is an integer.
	TagNotesGap = "NOTESGAP"

	// TagStart specifies the number of seconds into a song where singing should start.
	// This can be used for testing or to skip long intros.
	//
	// The value is a floating point number.
	TagStart = "START"

	// TagEnd specifies the number of milliseconds into a song where singing should end.
	// This can be used for testing or to skip long outros.
	//
	// The value is an integer.
	TagEnd = "END"

	// TagResolution seems to be relevant only in XML formatted songs.
	// The exact purpose is unclear.
	//
	// The value is an integer.
	TagResolution = "RESOLUTION"

	// TagPreviewStart specifies the number of seconds into a song where the preview should start.
	//
	// The value is a floating point number.
	TagPreviewStart = "PREVIEWSTART"

	// TagMedleyStartBeat identifies the beat of the song where the medley starts.
	//
	// The value is an integer.
	TagMedleyStartBeat = "MEDLEYSTARTBEAT"

	// TagMedleyEndBeat identifies the beat of the song where the medley ends.
	//
	// The value is an integer.
	TagMedleyEndBeat = "MEDLEYENDBEAT"

	// TagCalcMedley can be set to "OFF" to disable the automatic medley and preview detection algorithms in UltraStar.
	// Other values are not supported.
	//
	// Manually setting medley start and end beat has the same effect.
	TagCalcMedley = "CALCMEDLEY"

	// TagTitle specifies the title/name of the song.
	TagTitle = "TITLE"

	// TagArtist specifies the artist of the song.
	TagArtist = "ARTIST"

	// TagGenre specifies the genre of the song.
	TagGenre = "GENRE"

	// TagEdition specifies the edition of the song.
	// The edition was originally intended as a way to categorize the original SingStar editions
	// but is often used as an arbitrary category value.
	TagEdition = "EDITION"

	// TagCreator identifies the creator of the song file.
	// This should be considered equal to TagAuthor.
	TagCreator = "CREATOR"

	// TagAuthor identifies the creator of the song file.
	// This should be considered equal to TagCreator.
	TagAuthor = "AUTHOR"

	// TagLanguage identifies the language of the song.
	// This does not have an impact on the song's lyrics but is only used as metadata for categorizing songs.
	TagLanguage = "LANGUAGE"

	// TagYear identifies the release year of the song.
	//
	// The value must be an integer.
	TagYear = "YEAR"

	// TagComment adds an arbitrary comment to a song.
	TagComment = "COMMENT"

	// TagDuetSingerP1 specifies the name of the first duet singer.
	// This tag should be considered equivalent to TagP1.
	TagDuetSingerP1 = "DUETSINGERP1"

	// TagDuetSingerP2 specifies the name of the second duet singer.
	// This tag should be considered equivalent to TagP2.
	TagDuetSingerP2 = "DUETSINGERP2"

	// TagP1 specifies the name of the first duet singer.
	// This tag should be considered equivalent to TagDuetSingerP1.
	TagP1 = "P1"

	// TagP2 specifies the name of the first duet singer.
	// This tag should be considered equivalent to TagDuetSingerP2.
	TagP2 = "P2"
)

These are the tags recognized by this package (in their canonical format).

Variables

View Source
var (
	// ErrEmptyLine indicates that an empty line was encountered but not expected.
	ErrEmptyLine = errors.New("unexpected empty line")
	// ErrInvalidPNumber indicates that a player change was not correctly formatted.
	ErrInvalidPNumber = errors.New("invalid P-number")
	// ErrUnexpectedPNumber indicates that a player change was found in a non-duet song.
	ErrUnexpectedPNumber = errors.New("unexpected P number")
	// ErrInvalidNote indicates that a note could not be parsed.
	ErrInvalidNote = errors.New("invalid note")
	// ErrInvalidLineBreak indicates that a line break could not be parsed.
	ErrInvalidLineBreak = errors.New("invalid line break")
	// ErrInvalidBPMChange indicates that a BPM change could not be parsed.
	ErrInvalidBPMChange = errors.New("invalid BPM change")
	// ErrInvalidEndTag indicates that the end tag of a song was not correctly formatted.
	ErrInvalidEndTag = errors.New("invalid end tag")
	// ErrMissingEndTag indicates that no end tag was found.
	ErrMissingEndTag = errors.New("missing end tag")
	// ErrRelativeNotAllowed indicates that a song is in relative mode but the parser dialect forbids it.
	ErrRelativeNotAllowed = errors.New("RELATIVE tag not allowed")
	// ErrUnknownEvent indicates that the parser encountered a line starting with an unsupported symbol.
	ErrUnknownEvent = errors.New("invalid event")
	// ErrUnknownEncoding indicates that the value of the #ENCODING tag was not understood.
	ErrUnknownEncoding = errors.New("unknown encoding")
)

These are known errors that occur during parsing. Note that the parser wraps these in an ParseError value.

View Source
var DialectDefault = &Dialect{
	AllowBOM:                true,
	ApplyEncoding:           true,
	IgnoreEmptyLines:        true,
	IgnoreLeadingSpaces:     false,
	AllowRelative:           true,
	StrictLineBreaks:        true,
	EndTagRequired:          false,
	StrictEndTag:            true,
	AllowInternationalFloat: true,
}

DialectDefault is the default dialect used for parsing UltraStar songs. The default dialect expects a more strict variant of songs.

View Source
var DialectUltraStar = &Dialect{
	AllowBOM:                true,
	ApplyEncoding:           true,
	IgnoreEmptyLines:        false,
	IgnoreLeadingSpaces:     false,
	AllowRelative:           true,
	StrictLineBreaks:        false,
	EndTagRequired:          false,
	StrictEndTag:            false,
	AllowInternationalFloat: true,
}

DialectUltraStar is a parser dialect that very closely resembles the behavior of the TXT parser implementation of UltraStar Deluxe.

View Source
var (
	// ErrBPMMismatch indicates that the different voices of a duet have different BPMs.
	// The UltraStar TXT format does not support this scenario.
	ErrBPMMismatch = errors.New("duet voices have different BPMs")
)

These errors can occur while writing a song to TXT format.

View Source
var FormatDefault = &Format{
	FieldSeparator: ' ',
	Relative:       false,
	CommaFloat:     false,
}

FormatDefault is the default format. The default format is fully compatible with all known Karaoke games.

View Source
var FormatRelative = &Format{
	FieldSeparator: ' ',
	Relative:       true,
	CommaFloat:     false,
}

FormatRelative is equal to the FormatDefault but will write songs in relative mode. Relative mode is basically deprecated and should only be used for good reasons.

Functions

func CanonicalTagName

func CanonicalTagName(name string) string

CanonicalTagName returns the normalized version of the specified tag name (that is: the uppercase version).

func GetTag

func GetTag(s *ultrastar.Song, tag string) string

GetTag serializes the specified tag from s and returns it. This is a convenience function for Format.GetTag.

func ParseNote

func ParseNote(s string) (ultrastar.Note, error)

ParseNote parses s into an ultrastar.Note. This is a convenience function for Dialect.ParseNoteRelative.

func ParseSong

func ParseSong(s string) (*ultrastar.Song, error)

ParseSong parses s into a song. This is a convenience method for Dialect.ReadSong.

func ReadSong

func ReadSong(r io.Reader) (*ultrastar.Song, error)

ReadSong parses a song from r. This is a convenience method for Dialect.ReadSong.

Note that r is not necessarily read completely.

func SetTag

func SetTag(s *ultrastar.Song, tag string, value string) error

SetTag sets a tag as found in a TXT file on a song. This is a convenience function for Dialect.SetTag.

func TransformMusic

func TransformMusic(m *ultrastar.Music, t transform.Transformer) error

TransformMusic applies t to the text of every note in m. If an error occurs the return value will be of type TransformError and have its NoteErrors field set. The note text that caused the error will remain unchanged. Even if an error occurs the remaining music will still be transformed.

func TransformSong

func TransformSong(s *ultrastar.Song, t transform.Transformer) error

TransformSong applies the given transform.Transformer to all texts in s. All texts include tag values, custom tag names, and all note texts.

If an error occurs during the transformation of a tag or note the error will be recorded and the tag or note will not be modified. The transformation however will continue through the entire song. The error returned is a TransformError that allows you to inspect the places that caused errors.

func WriteSong

func WriteSong(w io.Writer, s *ultrastar.Song) error

WriteSong serializes s into w. This is a convenience method for Format.WriteSong.

Types

type Dialect

type Dialect struct {
	// AllowBOM controls whether the parser should support songs that have an explicit Byte Order Mark.
	// If set to true the parser will understand and decode UTF-8 and UTF-16 BOMs.
	AllowBOM bool
	// ApplyEncoding controls whether the #ENCODING tag interpreted and applied to the song.
	// If it is not applied it will be treated as a custom tag.
	// If the encoding contains a value the parser does not understand it custom tag will be present as well.
	ApplyEncoding bool
	// IgnoreEmptyLines specifies whether empty lines are allowed in songs.
	IgnoreEmptyLines bool
	// IgnoreLeadingSpaces controls whether leading spaces are ignored in songs.
	// This only applies to the beginning of lines and not e.g. to note texts.
	IgnoreLeadingSpaces bool
	// AllowRelative controls whether the parser allows parsing of songs in relative mode.
	AllowRelative bool
	// StrictLineBreaks controls whether line breaks can have additional text on their line.
	// If set to true the parser will return an error if a line break has additional text.
	StrictLineBreaks bool
	// EndTagRequired controls whether the final 'E' is required.
	EndTagRequired bool
	// StrictEndTag controls whether any line starting with 'E' counts as an end tag.
	// If set to true only a single 'E' may be on the ending line.
	StrictEndTag bool
	// AllowInternationalFloat controls whether floats can use a comma as the decimal separator.
	AllowInternationalFloat bool
}

A Dialect modifies the behavior of the parser. Using a Dialect you control how strict the parser will be when it comes to certain inaccuracies. This is analogous to the Format of the writer.

Methods on Dialect values are safe for concurrent use by multiple goroutines as long as the dialect value remains unchanged.

func (*Dialect) ParseNote

func (d *Dialect) ParseNote(s string) (ultrastar.Note, error)

ParseNote parses s into an ultrastar.Note. This is a convenience function for Dialect.ParseNoteRelative.

func (*Dialect) ParseNoteRelative

func (d *Dialect) ParseNoteRelative(s string, relative bool) (ultrastar.Note, error)

ParseNoteRelative converts an UltraStar-style note line into an ultrastar.Note instance. There are two formats:

Regular notes follow the format "X A B C Text" where

  • X is a single character denoting the ultrastar.NoteType. The note type must be valid as determined by ultrastar.NoteType.IsValid.
  • A is an integer denoting the start beat of the note
  • B is an integer denoting the duration of the note
  • C is an integer denoting the pitch of the note
  • Text is the remaining text on the line denoting the text of the syllable.

Line breaks follow the format "X A" or "X A B" where

  • X is the character '-' (dash)
  • A is an integer denoting the start beat of the line break
  • B is an integer denoting the relative offset of the next line. This format is only used when relative is true.

If an error occurs the returned note may be partially initialized. However, this behavior should not be relied upon.

func (*Dialect) ReadMusic

func (d *Dialect) ReadMusic(r io.Reader) (*ultrastar.Music, error)

ReadMusic parses an ultrastar.Music from r. If the music ends with an end tag (a line starting with 'E') r may not be read until the end.

If an error occurs this method may return a partial parse result up until the error occurred.

func (*Dialect) ReadSong

func (d *Dialect) ReadSong(r io.Reader) (*ultrastar.Song, error)

ReadSong parses an ultrastar.Song from r. If the song ends with an end tag (a line starting with 'E') r may not be read until the end.

The song returned by this method will always be in absolute time. If the source file uses relative mode the times will be converted to absolute times.

If an error occurs this method may return a partial result up until the error occurred. The concrete type of the error can be an instance of ParseError or TransformError indicating that the error occurred during parsing or decoding. It may also be an error value such as ErrUnknownEncoding.

func (*Dialect) SetTag

func (d *Dialect) SetTag(s *ultrastar.Song, tag string, value string) error

SetTag parses the specified tag (as it would be present in an UltraStar file) and stores it in the appropriate field in s. If the tag does not correspond to any known tag it is stored in ultrastar.Song.CustomTags of s.

This method converts the value to appropriate data types for known values. If an error occurs during conversion it is returned. Otherwise, nil is returned.

type Format

type Format struct {
	// FieldSeparator is a character used to separate fields in note line and line breaks.
	// This should only be set to a space or tab.
	//
	// Characters other than space or tab may or may not work and
	// will most likely result in invalid songs.
	FieldSeparator rune

	// Relative indicates that the writer will write music in relative mode.
	// This is a legacy format that is not recommended anymore.
	Relative bool

	// CommaFloat indicates that floating point values should use a comma as decimal separator.
	CommaFloat bool
}

A Format defines how an ultrastar.Song is serialized to TXT. This is analogous to the Dialect of the parser.

Methods on Format values are safe for concurrent use by multiple goroutines as long as the dialect value remains unchanged.

func (*Format) GetTag

func (f *Format) GetTag(s *ultrastar.Song, tag string) string

GetTag serializes the specified tag from song s and returns it. Known tags are resolved to the appropriate fields in ultrastar.Song, other tags are fetched from the custom tags.

This method does not differentiate between tags that are absent and tags that have an empty value. From an UltraStar perspective the two are identical. If you need to know if a custom tag exists or not, access the custom tags directly.

For numeric tags an empty string is returned instead of a 0 value.

func (*Format) WriteMusic

func (f *Format) WriteMusic(w io.Writer, m *ultrastar.Music) error

WriteMusic writes all notes, line breaks and BPM changes in m in standard UltraStar format.

Depending on the value of f.Relative the music may be written in relative mode. A #RELATIVE tag is NOT written automatically in this case.

func (*Format) WriteNote

func (f *Format) WriteNote(w io.Writer, n ultrastar.Note) error

WriteNote writes a single note line. The note is written as-is, even if w is in relative mode.

func (*Format) WriteNoteRel

func (f *Format) WriteNoteRel(w io.Writer, n ultrastar.Note, rel *ultrastar.Beat) error

WriteNoteRel writes a single note. If f.Relative is true, the note start is adjusted by rel. If n is a line break, rel is updated accordingly.

func (*Format) WriteSong

func (f *Format) WriteSong(w io.Writer, s *ultrastar.Song) error

WriteSong writes the song s to w in the UltraStar txt format. If an error occurs it is returned, otherwise nil is returned.

func (*Format) WriteTag

func (f *Format) WriteTag(w io.Writer, tag string, value string) error

WriteTag writes a single tag. Neither the tag nor the value are validated or normalized, they are written as-is.

type ParseError

type ParseError struct {
	// contains filtered or unexported fields
}

ParseError is an error type that may be returned by the parsing methods. It wraps an underlying error and also provides a line number on which the error occurred.

func (ParseError) Error

func (e ParseError) Error() string

Error returns the error string.

func (ParseError) Line

func (e ParseError) Line() int

Line returns the line number that caused the error.

func (ParseError) Unwrap

func (e ParseError) Unwrap() error

Unwrap returns the underlying error.

type TransformError

type TransformError struct {
	// errors occurred when transforming tag values. The key is the tag name for
	// which the value could not be transformed.
	TagErrors map[string]error
	// errors occurred when transforming custom tag keys. The key is the (untransformed)
	// tag name that could not be transformed.
	TagKeyErrors map[string]error
	// errors occurred when transforming note texts. The key is the note index
	// that caused the error.
	NoteErrors map[int]error
}

A TransformError indicates an error that occurred during applying a transform.Transformer to an ultrastar.Song. Depending on the function used different fields may be nil or an empty map.

func (*TransformError) Error

func (e *TransformError) Error() string

Error implements the error interface.

func (*TransformError) Unwrap

func (e *TransformError) Unwrap() []error

Unwrap implements the error interface.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL