Skip to content
This repository was archived by the owner on Jun 15, 2024. It is now read-only.

Commit 0689547

Browse files
authored
Add option to extract id from URL (#13)
1 parent aab6e13 commit 0689547

File tree

2 files changed

+61
-24
lines changed

2 files changed

+61
-24
lines changed

Sources/M3UKit/PlaylistParser.swift

+32-20
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,14 @@ public final class PlaylistParser {
3737
/// Remove season number and episode number "S--E--" from the name of media.
3838
public static let removeSeriesInfoFromText = Options(rawValue: 1 << 0)
3939

40+
/// Extract id from the URL (usually last path component removing the extension)
41+
public static let extractIdFromURL = Options(rawValue: 1 << 1)
42+
4043
/// All available options.
41-
public static let all: Options = [.removeSeriesInfoFromText]
44+
public static let all: Options = [
45+
.removeSeriesInfoFromText,
46+
.extractIdFromURL,
47+
]
4248
}
4349

4450
/// Parser options.
@@ -77,7 +83,7 @@ public final class PlaylistParser {
7783

7884
if let metadataLine = lastMetadataLine, let url = lastURL {
7985
do {
80-
let metadata = try self.parseMetadata((lineNumber, metadataLine))
86+
let metadata = try self.parseMetadata(line: lineNumber, rawString: metadataLine, url: url)
8187
let kind = self.parseMediaKind(url)
8288
medias.append(.init(metadata: metadata, kind: kind, url: url))
8389
lastMetadataLine = nil
@@ -127,7 +133,7 @@ public final class PlaylistParser {
127133

128134
if let metadataLine = lastMetadataLine, let url = lastURL {
129135
do {
130-
let metadata = try self.parseMetadata((lineNumber, metadataLine))
136+
let metadata = try self.parseMetadata(line: lineNumber, rawString: metadataLine, url: url)
131137
let kind = self.parseMediaKind(url)
132138
handler(.init(metadata: metadata, kind: kind, url: url))
133139
lastMetadataLine = nil
@@ -216,23 +222,23 @@ public final class PlaylistParser {
216222

217223
internal typealias Show = (name: String, se: (s: Int, e: Int)?)
218224

219-
internal func parseMetadata(_ input: (line: Int, rawString: String)) throws -> Playlist.Media.Metadata {
220-
let duration = try extractDuration(input)
221-
let attributes = parseAttributes(input.rawString)
222-
let name = parseSeasonEpisode(extractName(input.rawString)).name
225+
internal func parseMetadata(line: Int, rawString: String, url: URL) throws -> Playlist.Media.Metadata {
226+
let duration = try extractDuration(line: line, rawString: rawString)
227+
let attributes = parseAttributes(rawString: rawString, url: url)
228+
let name = parseSeasonEpisode(extractName(rawString)).name
223229
return (duration, attributes, name)
224230
}
225231

226232
internal func isInfoLine(_ input: String) -> Bool {
227233
return input.starts(with: "#EXTINF:")
228234
}
229235

230-
internal func extractDuration(_ input: (line: Int, rawString: String)) throws -> Int {
236+
internal func extractDuration(line: Int, rawString: String) throws -> Int {
231237
guard
232-
let match = durationRegex.firstMatch(in: input.rawString),
238+
let match = durationRegex.firstMatch(in: rawString),
233239
let duration = Int(match)
234240
else {
235-
throw ParsingError.missingDuration(input.line, input.rawString)
241+
throw ParsingError.missingDuration(line, rawString)
236242
}
237243
return duration
238244
}
@@ -241,6 +247,10 @@ public final class PlaylistParser {
241247
return nameRegex.firstMatch(in: input) ?? ""
242248
}
243249

250+
internal func extractId(_ input: URL) -> String {
251+
String(input.lastPathComponent.split(separator: ".").first ?? "")
252+
}
253+
244254
internal func parseMediaKind(_ input: URL) -> Playlist.Media.Kind {
245255
let string = input.absoluteString
246256
if mediaKindMSeriesRegex.numberOfMatches(source: string) == 1 {
@@ -255,33 +265,35 @@ public final class PlaylistParser {
255265
return .unknown
256266
}
257267

258-
internal func parseAttributes(_ input: String) -> Playlist.Media.Attributes {
268+
internal func parseAttributes(rawString: String, url: URL) -> Playlist.Media.Attributes {
259269
var attributes = Playlist.Media.Attributes()
260-
if let id = attributesIdRegex.firstMatch(in: input) {
261-
attributes.id = id
270+
let id = attributesIdRegex.firstMatch(in: rawString) ?? ""
271+
attributes.id = id
272+
if id.isEmpty && options.contains(.extractIdFromURL) {
273+
attributes.id = extractId(url)
262274
}
263-
if let name = attributesNameRegex.firstMatch(in: input) {
275+
if let name = attributesNameRegex.firstMatch(in: rawString) {
264276
let show = parseSeasonEpisode(name)
265277
attributes.name = show.name
266278
attributes.seasonNumber = show.se?.s
267279
attributes.episodeNumber = show.se?.e
268280
}
269-
if let country = attributesCountryRegex.firstMatch(in: input) {
281+
if let country = attributesCountryRegex.firstMatch(in: rawString) {
270282
attributes.country = country
271283
}
272-
if let language = attributesLanguageRegex.firstMatch(in: input) {
284+
if let language = attributesLanguageRegex.firstMatch(in: rawString) {
273285
attributes.language = language
274286
}
275-
if let logo = attributesLogoRegex.firstMatch(in: input) {
287+
if let logo = attributesLogoRegex.firstMatch(in: rawString) {
276288
attributes.logo = logo
277289
}
278-
if let channelNumber = attributesChannelNumberRegex.firstMatch(in: input) {
290+
if let channelNumber = attributesChannelNumberRegex.firstMatch(in: rawString) {
279291
attributes.channelNumber = channelNumber
280292
}
281-
if let shift = attributesShiftRegex.firstMatch(in: input) {
293+
if let shift = attributesShiftRegex.firstMatch(in: rawString) {
282294
attributes.shift = shift
283295
}
284-
if let groupTitle = attributesGroupTitleRegex.firstMatch(in: input) {
296+
if let groupTitle = attributesGroupTitleRegex.firstMatch(in: rawString) {
285297
attributes.groupTitle = groupTitle
286298
}
287299
return attributes

Tests/M3UKitTests/PlaylistParserTests.swift

+29-4
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ final class PlaylistParserTests: XCTestCase {
128128
func testExtractingDuration() throws {
129129
let parser = PlaylistParser()
130130

131-
XCTAssertThrowsError(try parser.extractDuration((1, "invalid")))
131+
XCTAssertThrowsError(try parser.extractDuration(line: 1, rawString: "invalid"))
132132
}
133133

134134
func testExtractingName() throws {
@@ -138,6 +138,13 @@ final class PlaylistParserTests: XCTestCase {
138138
XCTAssertEqual(parser.extractName(",valid"), "valid")
139139
}
140140

141+
func testExtractingIdFromURL() {
142+
let parser = PlaylistParser()
143+
144+
let url = URL(string: "https://domain.com/live/username/password/123456.mp4")!
145+
XCTAssertEqual(parser.extractId(url), "123456")
146+
}
147+
141148
func testIsInfoLine() {
142149
let parser = PlaylistParser()
143150

@@ -148,10 +155,10 @@ final class PlaylistParserTests: XCTestCase {
148155
func testParsingAttributes() {
149156
let rawMedia = """
150157
#EXTINF:-1 tvg-name="DWEnglish.de" tvg-id="DWEnglish.de" tvg-country="INT" tvg-language="English" tvg-logo="https://i.imgur.com/A1xzjOI.png" tvg-chno="1" tvg-shift="0" group-title="News",DW English (1080p)
151-
https://dwamdstream102.akamaized.net/hls/live/2015525/dwstream102/index.m3u8
152158
"""
153-
let parser = PlaylistParser()
154-
let attributes = parser.parseAttributes(rawMedia)
159+
let url = URL(string: "https://dwamdstream102.akamaized.net/hls/live/2015525/dwstream102/index.m3u8")!
160+
let parser = PlaylistParser(options: .extractIdFromURL)
161+
let attributes = parser.parseAttributes(rawString: rawMedia, url: url)
155162
XCTAssertEqual(attributes.name, "DWEnglish.de")
156163
XCTAssertEqual(attributes.id, "DWEnglish.de")
157164
XCTAssertEqual(attributes.country, "INT")
@@ -162,6 +169,24 @@ https://dwamdstream102.akamaized.net/hls/live/2015525/dwstream102/index.m3u8
162169
XCTAssertEqual(attributes.groupTitle, "News")
163170
}
164171

172+
func testParsingAttributesWithOverridingId() {
173+
let rawMedia = """
174+
#EXTINF:-1 tvg-name="DWEnglish.de" tvg-id="" tvg-country="INT" tvg-language="English" tvg-logo="https://i.imgur.com/A1xzjOI.png" tvg-chno="1" tvg-shift="0" group-title="News",DW English (1080p)
175+
"""
176+
let url = URL(string: "https://domain.com/live/username/password/123456.mp4")!
177+
let parser = PlaylistParser(options: .extractIdFromURL)
178+
let attributes = parser.parseAttributes(rawString: rawMedia, url: url)
179+
XCTAssertEqual(attributes.name, "DWEnglish.de")
180+
XCTAssertEqual(attributes.id, "123456")
181+
XCTAssertEqual(attributes.country, "INT")
182+
XCTAssertEqual(attributes.language, "English")
183+
XCTAssertEqual(attributes.logo, "https://i.imgur.com/A1xzjOI.png")
184+
XCTAssertEqual(attributes.channelNumber, "1")
185+
XCTAssertEqual(attributes.shift, "0")
186+
XCTAssertEqual(attributes.groupTitle, "News")
187+
}
188+
189+
165190
func testSeasonEpisodeParsing() {
166191
let parser = PlaylistParser()
167192
let input = "Kyou Kara Ore Wa!! LIVE ACTION S01 E09"

0 commit comments

Comments
 (0)