txt

package
v0.0.0-...-93ceed9 Latest Latest
Warning

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

Go to latest
Published: Dec 28, 2023 License: MIT Imports: 13 Imported by: 3

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 notes 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 is an obscure tag that should not be used.
	// In ultrastar this identifies an offset for the click track if you have beat clicks turned on.
	// This library treats this as a custom tag with no special meaning.
	//
	// 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 is a tag that pops up in old documentation from time to time.
	// In TXT based songs this tag does not have any effect.
	// This tag originates from songs that were parsed from MIDI files (where the resolution does have an effect).
	// This library treats this as a custom tag with no special meaning.
	//
	// The value is an integer, an absent value is equivalent to 4.
	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 (
	// ErrMultiBPM indicates that a song uses a BPM change marker which is not supported.
	ErrMultiBPM = errors.New("multi BPM songs are not supported")
	// 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")
	// 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.

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 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 ParseNote

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

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

func ParseNoteRelative

func 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 ParseSong

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

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

func SetTag

func 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 s.CustomTags.

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.

func TransformNotes

func TransformNotes(ns ultrastar.Notes, t transform.Transformer) error

TransformNotes applies t to the text of every note in ns. 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 notes 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 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 Reader

type Reader 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
	// IgnoreBPMChanges controls whether the parser silently ignores BPM change markers.
	IgnoreBPMChanges bool

	// Relative indicates whether the parser is in relative mode.
	// After parsing a song you can use this field to determine whether the song was originally in relative mode.
	Relative bool
	// Encoding is the encoding used to decode textual data.
	// During parsing this will be set to the appropriate header field of the song,
	// unless it has been set explicitly.
	Encoding string
	// contains filtered or unexported fields
}

Reader implements the parser for the UltraStar TXT format.

func NewReader

func NewReader(rd io.Reader) *Reader

NewReader creates a new Reader instance reading from rd. You can configure r before you start reading. After the first Read* call on the returned reader it is not guaranteed that configuration changes will be respected.

The reader uses default settings that result in more strict parsing behavior compared to the UltraStar parser. Use Reader.UseUltraStarDialect to configure the reader to match UltraStar's parser more closely.

func (*Reader) ReadNotes

func (r *Reader) ReadNotes() (ultrastar.Notes, error)

ReadNotes parses an ultrastar.Notes from r. If the notes end 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 (*Reader) ReadSong

func (r *Reader) ReadSong() (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 (*Reader) ReadTags

func (r *Reader) ReadTags() (ultrastar.Song, error)

ReadTags reads a set of tags from the input and returns a song with the tags set. If an error occurs, it is returned.

func (*Reader) Reset

func (r *Reader) Reset(rd io.Reader)

Reset configures r to read from r, just like NewReader(rd) would. r keeps its configuration, however r.Relative and r.Encoding are reset.

Note that because Reader sometimes reads ahead, r.Reset(r.rd) may produce unexpected results.

func (*Reader) UseUltraStarDialect

func (r *Reader) UseUltraStarDialect()

UseUltraStarDialect configures r to match the behavior of the UltraStar TXT parser as closely as possible.

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.

type Writer

type Writer 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 notes 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
	// contains filtered or unexported fields
}

A Writer implements serialization of ultrastar.Song serialized to TXT.

func NewWriter

func NewWriter(wr io.Writer) *Writer

NewWriter creates a new writer for UltraStar songs. The default settings aim to be compatible with most Karaoke games.

func (*Writer) Reset

func (w *Writer) Reset(wr io.Writer)

Reset configures w to be reused, writing to wr. This method keeps the current writer's configuration.

func (*Writer) WriteNote

func (w *Writer) WriteNote(n ultrastar.Note) error

WriteNote writes a single note line. Depending on w.Relative the note is adjusted to the current relative offset.

func (*Writer) WriteNotes

func (w *Writer) WriteNotes(ns ultrastar.Notes) error

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

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

func (*Writer) WriteSong

func (w *Writer) WriteSong(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 (*Writer) WriteTag

func (w *Writer) WriteTag(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.

Jump to

Keyboard shortcuts

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