Passed
Push — master ( b191fa...ec9cdb )
by Pauli
04:01
created

LyricsParser::parseTimestampedLrcLine()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 5
eloc 15
nc 4
nop 2
dl 0
loc 28
ccs 0
cts 16
cp 0
crap 30
rs 9.4555
c 3
b 0
f 0
1
<?php
2
3
/**
4
 * ownCloud - Music app
5
 *
6
 * This file is licensed under the Affero General Public License version 3 or
7
 * later. See the COPYING file.
8
 *
9
 * @author Pauli Järvinen <[email protected]>
10
 * @copyright Pauli Järvinen 2020
11
 */
12
13
namespace OCA\Music\Utility;
14
15
16
class LyricsParser {
17
18
	/**
19
	 * Take the timestamped lyrics as returned by `LyricsParser::parseSyncedLyrics` and
20
	 * return the corresponding plain text representation with no LRC tags.
21
	 * Input value null will give null result.
22
	 *
23
	 * @param array|null $parsedSyncedLyrics
24
	 * @return string|null
25
	 */
26
	public static function syncedToUnsynced($parsedSyncedLyrics) {
27
		return ($parsedSyncedLyrics === null) ? null : \implode("\n", $parsedSyncedLyrics);
28
	}
29
30
	/**
31
	 * Parse timestamped lyrics from the given string, and return the parsed data.
32
	 * Return null if the string does not appear to be timestamped lyric in the LRC format.
33
	 *
34
	 * @param string $data
35
	 * @return array|null The keys of the array are timestamps in milliseconds and values are
36
	 *                    corresponding lines of lyrics.
37
	 */
38
	public static function parseSyncedLyrics($data) {
39
		$parsedLyrics = [];
40
		$offset = 0;
41
42
		$fp = \fopen("php://temp", 'r+');
43
		\fputs($fp, $data);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fputs() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

43
		\fputs(/** @scrutinizer ignore-type */ $fp, $data);
Loading history...
44
		\rewind($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of rewind() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

44
		\rewind(/** @scrutinizer ignore-type */ $fp);
Loading history...
45
		while ($line = \fgets($fp)) {
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fgets() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

45
		while ($line = \fgets(/** @scrutinizer ignore-type */ $fp)) {
Loading history...
46
			$lineParseResult = self::parseTimestampedLrcLine($line, $offset);
47
			$parsedLyrics += $lineParseResult;
48
		}
49
		\fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

49
		\fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
50
51
		// sort the parsed lyric lines according the timestamps (which are keys of the array)
52
		\ksort($parsedLyrics);
53
54
		return \count($parsedLyrics) > 0 ? $parsedLyrics : null;
55
	}
56
57
	/**
58
	 * Parse a single line of LRC formatted data. The result is array where keys are
59
	 * timestamps and values are corresponding lyrics. A single line of LRC may span out to
60
	 * a) 0 actual timestamp lines, if the line is empty, or contains just metadata, or contains no tags
61
	 * b) 1 actual timestamp line (the "normal" case)
62
	 * c) several actual timestamp lines, in case the line contains several timestamps,
63
	 *    meaning that the same line of text is repeated multiple times during the song
64
	 *
65
	 * If the line defines a time offset, this is returned in the reference paramete. If the offset
66
	 * parameter holds a non-zero value on call, the offset is applied on any extracted timestamps. 
67
	 *
68
	 * @param string $line
69
	 * @param int [in|out] $offset
0 ignored issues
show
Documentation Bug introduced by
The doc comment [in|out] at position 0 could not be parsed: Unknown type name '[' at position 0 in [in|out].
Loading history...
70
	 * @return array
71
	 */
72
	private static function parseTimestampedLrcLine($line, &$offset) {
73
		$result = [];
74
		$line = \trim($line);
75
76
		$matches = [];
77
		if (\preg_match('/(\[.+\])(.*)/', $line, $matches)) {
78
			// 1st group captures tag(s), 2nd group anything after the tag(s).
79
			$tags = $matches[1];
80
			$text = $matches[2];
81
82
			// Extract timestamp tags and the offset tag and discard any other metadata tags.
83
			$timestampMatches = [];
84
			$offsetMatch = [];
85
			if (\preg_match('/\[offset:(\d+)\]/', $tags, $offsetMatch)) {
86
				$offset = \intval($offsetMatch[1]);
87
			}
88
			elseif (\preg_match_all('/\[(\d\d:\d\d\.\d\d)\]/', $tags, $timestampMatches)) {
89
				// some timestamp(s) were found
90
				$timestamps = $timestampMatches[1];
91
92
				// add the line text to the result set on each found timestamp
93
				foreach ($timestamps as $timestamp) {
94
					$result[self::timestampToMs($timestamp) - $offset] = $text;
95
				}
96
			}
97
		}
98
99
		return $result;
100
	}
101
102
	/**
103
	 * Convert timestamp in "mm:ss.ff" format to milliseconds
104
	 * 
105
	 * @param string $timestamp
106
	 * @return int
107
	 */
108
	private static function timestampToMs($timestamp) {
109
		\sscanf($timestamp, "%d:%f", $minutes, $seconds);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $seconds seems to be never defined.
Loading history...
110
		return \intval($seconds * 1000 + $minutes * 60 * 1000);
111
	}
112
}
113