Example #1
0
        /// <summary>
        /// Initializes a new instance of the <see cref="LrcFile"/> class.
        /// </summary>
        /// <param name="metadata">The metadata.</param>
        /// <param name="lyrics">The lyrics.</param>
        /// <param name="applyOffset">if set to <c>true</c> apply the offset in metadata, otherwise ignore the offset.</param>
        public LrcFile(ILrcMetadata metadata, IEnumerable <IOneLineLyric> lyrics, bool applyOffset)
        {
            if (metadata == null)
            {
                throw new ArgumentNullException("metadata");
            }
            if (lyrics == null)
            {
                throw new ArgumentNullException("lyrics");
            }
            Metadata = metadata;
            var array = lyrics.OrderBy(l => l.Timestamp).ToArray();

            for (var i = 0; i < array.Length; ++i)
            {
                if (applyOffset && metadata.Offset.HasValue)
                {
                    var oneLineLyric = array[i] as OneLineLyric;
                    if (oneLineLyric != null)
                    {
                        oneLineLyric.Timestamp -= metadata.Offset.Value;
                    }
                    else
                    {
                        array[i] = new OneLineLyric(array[i].Timestamp - metadata.Offset.Value, array[i].Content);
                    }
                }
                if (i > 0 && array[i].Timestamp == array[i - 1].Timestamp)
                {
                    throw new FormatException(string.Format("Found duplicate timestamp '{0}' with lyric '{1}' and '{2}'.", array[i].Timestamp, array[i - 1].Content, array[i].Content));
                }
            }
            _lyrics = array;
        }
Example #2
0
        /// <summary>
        /// Create a new new instance of the <see cref="ILrcFile"/> interface with the specified LRC text.
        /// </summary>
        /// <param name="lrcText">The LRC text.</param>
        /// <param name="allowBracketInContent">If true, brackets are allowed in content and parsed as content (reserved tags are still parsed properly)</param>
        /// <returns></returns>
        public static ILrcFile FromText(string lrcText, bool allowBracketInContent = false)
        {
            if (lrcText == null)
            {
                throw new ArgumentNullException("lrcText");
            }
            var pairs  = new List <KeyValuePair <string, string> >();
            var titles = new List <string>();
            var sb     = new StringBuilder();

            // 0: Line start. Expect line ending or [.
            // 1: Reading title. Expect ] or all characters except line ending.
            // 2: Reading content. Expect line ending or [ or other charactors.
            var state = 0;

            for (var i = 0; i <= lrcText.Length; ++i)
            {
                var ended = i >= lrcText.Length;
                var ch    = ended ? (char)0 : lrcText[i];
                // ReSharper disable once IdentifierTypo
                var unescaped = false;
                if (ch == '\\')
                {
                    ++i;
                    ended = i >= lrcText.Length;
                    if (ended)
                    {
                        throw new FormatException("Expect one charactor after '\\' but reaches the end.");
                    }
                    ch        = lrcText[i];
                    unescaped = true;
                }

                switch (state)
                {
                case 0:
                    if (!unescaped && ch == '[')
                    {
                        state = 1;
                    }
                    else if (!unescaped && (ch == '\r' || ch == '\n') || ended)
                    {
                        state = 0;
                    }
                    else
                    {
                        throw new FormatException(string.Format("Expect '[' at position {0}", i));
                    }
                    break;

                case 1:
                    if (!unescaped && ch == ']')
                    {
                        state = 2;
                        titles.Add(sb.ToString());
                        sb.Clear();
                    }
                    else if (!unescaped && (ch == '\r' || ch == '\n') || ended)
                    {
                        throw new FormatException(string.Format("Expect ']' at position {0}", i));
                    }
                    else
                    {
                        sb.Append(ch);     // append to title
                    }
                    break;

                case 2:
                    if (!unescaped && (ch == '\r' || ch == '\n') || ended)
                    {
                        state = 0;
                        var content = sb.ToString();
                        pairs.AddRange(titles.Select(t => new KeyValuePair <string, string>(t, content)));
                        sb.Clear();
                        titles.Clear();
                    }
                    else if (!unescaped && ch == '[')
                    {
                        if (sb.Length > 0)
                        {
                            state = 1;
                            var content = sb.ToString();
                            pairs.AddRange(titles.Select(t => new KeyValuePair <string, string>(t, content)));
                            sb.Clear();
                            titles.Clear();
                        }
                        else
                        {
                            state = 1;
                        }
                    }
                    else
                    {
                        sb.Append(ch);
                    }
                    break;
                }
            }

            var    lyrics       = new List <IOneLineLyric>();
            var    metadata     = new LrcMetadata();
            string offsetString = null;

            foreach (var pair in pairs)
            {
                // Parse timestamp
                var match = TimestampRegex.Match(pair.Key);
                if (match.Success)
                {
                    var minutes   = int.Parse(match.Groups["minutes"].Value);
                    var seconds   = double.Parse(match.Groups["seconds"].Value);
                    var timestamp = TimeSpan.FromSeconds(minutes * 60 + seconds);
                    lyrics.Add(new OneLineLyric(timestamp, pair.Value));
                    continue;
                }

                // Parse metadata
                match = MetadataRegex.Match(pair.Key);
                if (match.Success)
                {
                    var title   = match.Groups["title"].Value.ToLower();
                    var content = match.Groups["content"].Value;
                    if (title == "ti")
                    {
                        if (metadata.Title != null && content != metadata.Title)
                        {
                            throw new FormatException(string.Format("Duplicate LRC metadata found. Metadata name: '{0}', Values: '{1}', '{2}'", "ti", metadata.Title, content));
                        }
                        metadata.Title = content;
                    }
                    else if (title == "ar")
                    {
                        if (metadata.Artist != null && content != metadata.Artist)
                        {
                            throw new FormatException(string.Format("Duplicate LRC metadata found. Metadata name: '{0}', Values: '{1}', '{2}'", "ar", metadata.Artist, content));
                        }
                        metadata.Artist = content;
                    }
                    else if (title == "al")
                    {
                        if (metadata.Album != null && content != metadata.Album)
                        {
                            throw new FormatException(string.Format("Duplicate LRC metadata found. Metadata name: '{0}', Values: '{1}', '{2}'", "al", metadata.Album, content));
                        }
                        metadata.Album = content;
                    }
                    else if (title == "by")
                    {
                        if (metadata.Maker != null && content != metadata.Maker)
                        {
                            throw new FormatException(string.Format("Duplicate LRC metadata found. Metadata name: '{0}', Values: '{1}', '{2}'", "by", metadata.Maker, content));
                        }
                        metadata.Maker = content;
                    }
                    else if (title == "offset")
                    {
                        if (offsetString != null && content != offsetString)
                        {
                            throw new FormatException(string.Format("Duplicate LRC metadata found. Metadata name: '{0}', Values: '{1}', '{2}'", "offset", offsetString, content));
                        }
                        offsetString    = content;
                        metadata.Offset = TimeSpan.FromMilliseconds(double.Parse(content));
                    }
                    // ReSharper disable once RedundantIfElseBlock
                    else
                    {
                        // Ingore unsupported tag
                    }
                }
                else
                {
                    if (allowBracketInContent)
                    {
                        var lastLyric = lyrics.LastOrDefault();
                        if (lastLyric != null)
                        {
                            lyrics.Remove(lastLyric);

                            var newLyric = new OneLineLyric(lastLyric.Timestamp,
                                                            string.Format("{0}[{1}]{2}", lastLyric.Content, pair.Key, pair.Value));
                            lyrics.Add(newLyric);
                        }
                    }
                    else
                    {
                        throw new FormatException(string.Format("Unknown tag [{0}]", pair.Key));
                    }
                }
            }

            if (lyrics.Count == 0)
            {
                throw new FormatException("Invalid or empty LRC text. Can't find any lyrics.");
            }
            return(new LrcFile(metadata, lyrics, true));
        }