这是核心解析代码的 1.0.0.0.0.0.0 版本,实现了数据提取,并没有任何代码优化和错误处理,属于逻辑验证过程
代码中可能含有未使用的参数,未使用的变量, 作为保留占位用
代码符合规范要求,对于标准格式的 m3u8 文件可以正确解析,提取,提取出的数据没有做保存
完整的代码待全部完善后再发,这是m3u8下载程序的一部分
m3u8_stream_load(UTF8数据流字节数组)
参数示例:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-KEY:METHOD=AES-128,URI="https://cdn.jkuyggfgb.cn/enc.key",IV=0x07f64b3f577ab4b1a7a832aabe9d9e77
#EXTINF:5.000000,
https://xxxt.com/short/8uyRPN36ykc%3D/001.ts
#EXTINF:5.000000,
https://xxxt.com/short/8uyRPN36ykc%3D/002.ts
#EXT-X-ENDLIST
m3u8_stream_load_node (以#开头到下一个#之前的UTF8数据流字节数组, 节点输出缓存)
参数示例:#EXT-X-KEY:METHOD=AES-128,URI="https://cdn.jkuyggfgb.cn/enc.key",IV=0x07f64b3f577ab4b1a7a832aabe9d9e77
m3u8_stream_load_node_value (被提取出来的值的内容-UTF8数据流字节数组, 节点输出缓存)
参数示例:METHOD=AES-128,URI="https://cdn.jkuyggfgb.cn/enc.key",IV=0x07f64b3f577ab4b1a7a832aabe9d9e77
m3u8_stream_load_node_attribs (被提取出来的属性内容-UTF8数据流字节数组, 节点输出缓存)
参数示例:METHOD=AES-128
m3u8_stream_cutpart_clearnullchar 参数略。。。
把输入的字节数组按照参数要求截取其中的一部分,在截取时一并把其中的的NullChar清除,返回一个新的字节数组
代码流程:
m3u8_stream_load
> m3u8_stream_cutpart_clearnullchar
> m3u8_stream_load_node
> m3u8_stream_load_node_value
> m3u8_stream_load_node_attribs
M3U8 的文件格式说明:
'// HTTP Live Streaming
'// RFC8216
'// https://datatracker.ietf.org/doc/html/rfc8216
1 Private Type m3u8_node_attribs 2 Name As String '// 属性名 3 Value As String '// 属性值 4 End Type 5 6 Private Type m3u8_node 7 Title As String '// 标签条目 名 8 Value As String '// 标签条目 值 9 Attribs() As m3u8_node_attribs '// 属性 10 AttribCount As Long 11 End Type 12 13 14 Private Function m3u8_stream_cutpart_clearnullchar(mulBits() As Byte, ByVal cutFrom As Long, ByVal cutSize As Long, ByVal chrFlag As Byte) As Byte() ' String 15 16 '// 从 mulBits 数组中,复制指定长度的字节数据,并同时把要复制数据的 NullChar 清除,组合成新的数组后返回 17 18 ' Select Case chrFlag 19 ' Case 10, 13, &HFF 20 ' '// 所有非法及无意义字符将被转换为 0,标志设置为 &hFF, 除11/13外 21 ' For i = 0 To cutSize - 1 22 ' Next 23 ' End Select 24 25 ' 定义一个字节数组,用于存储处理后的字节数据 26 Dim putBytes() As Byte 27 28 ' 定义一个长整型变量,用于循环计数 29 Dim i As Long 30 31 If cutSize = 0 Then MsgBox "复制长度为 0 的错误,无法识别为 0 时应该返回什么内容" & vbCrLf & StrConv(mulBits, vbUnicode): Stop 32 33 ' 重新调整 putBytes 数组的大小为 cutSize 34 ReDim putBytes(cutSize - 1) 35 36 ' 定义一些变量用于记录读写位置、复制的起始位置和复制的大小 37 Dim rwPoint As Long 38 Dim copyfrom As Long 39 Dim copySize As Long 40 Dim copyto As Long 41 42 copyto = cutFrom + cutSize - 1 43 44 ' 将 copyfrom 初始化为 cutFrom 45 copyfrom = cutFrom 46 47 ' 循环遍历从 cutFrom 到 cutTo 的范围 48 For i = cutFrom To cutFrom + cutSize - 1 49 50 ' Debug.Print Chr(mulBits(i)); 51 52 ' 如果当前字节为 0 53 If mulBits(i) = 0 Then 54 55 ' flag_null_char = True 56 57 ' 计算从 copyfrom 到当前位置的长度 58 copySize = i - copyfrom 59 60 ' 如果复制长度大于 0 61 If copySize Then 62 63 ' 使用 CopyMemory 函数将 mulBits 数组中从 copyfrom 开始的 copySize 个字节复制到 putBytes 数组中从 rwPoint 位置开始的地方 64 CopyMemory putBytes(rwPoint), mulBits(copyfrom), copySize 65 66 End If 67 68 ' 更新读写位置 69 rwPoint = rwPoint + copySize 70 71 ' 更新复制的起始位置为当前位置的下一个位置 72 copyfrom = i + 1 73 74 End If 75 Next 76 77 ' 计算最后一段的复制长度 78 copySize = copyto - copyfrom + 1 ' cutFrom + cutSize - 1 - copyfrom 79 80 ' 如果最后一段长度大于 0,将最后一段数据复制到 putBytes 数组中 81 If copySize > 0 Then CopyMemory putBytes(rwPoint), mulBits(copyfrom), copySize 82 83 ' 重新调整 putBytes 数组大小以保留实际使用的部分, 此时 rwpoint 读写点指向下一个可读写位置所以要 -1 84 ReDim Preserve putBytes(rwPoint + copySize - 1) 85 86 ' 使用 StrConv 函数将 putBytes 数组转换为 Unicode 字符串并返回 87 ' m3u8_stream_cutpart_clearnullchar = StrConv(putBytes, vbUnicode) 88 m3u8_stream_cutpart_clearnullchar = putBytes 89 90 End Function 91 92 93 94 Private Sub m3u8_stream_load_node_attribs(mulBits() As Byte, iopNode As m3u8_node) 95 '// 识别是否具有属性 96 '// 分析的数据示例: 97 '// #EXT-X-KEY:METHOD=AES-128,URI=https://askbfcdn.com/20241025/maEwILYb/2000kb/hls/key.key,IV=0x00000000000000000000000000000000 98 '// ~~~~~~~~~~~~~~ 99 100 Dim BoundOfArray As Long 101 Dim iCount As Long 102 103 Dim byteOfTitle() As Byte 104 Dim strTitle As String 105 106 Dim byteOfValue() As Byte 107 Dim strValue As String 108 109 BoundOfArray = UBound(mulBits) 110 111 '// 如果属性内容的 首字符或末位字符为 = ,则此属性无效,直接忽略 112 If mulBits(0) = 61 Then Exit Sub 113 If mulBits(BoundOfArray) = 61 Then Exit Sub 114 115 For iCount = 0 To BoundOfArray 116 117 '// 查找 = 118 If mulBits(iCount) = 61 Then ' "=" 119 120 '// = 的两边分别为 属性名 121 ReDim byteOfTitle(iCount - 1) 122 CopyMemory byteOfTitle(0), mulBits(0), iCount 123 strTitle = StrConv(byteOfTitle, vbUnicode) 124 125 '// 属性值 126 ReDim byteOfValue(BoundOfArray - iCount - 1) 127 CopyMemory byteOfValue(0), mulBits(iCount + 1), BoundOfArray - iCount 128 strValue = StrConv(byteOfValue, vbUnicode) 129 130 '// 只查找内容的第一个 = ,再有 = 也只视为值的一部分 131 Exit For 132 133 End If 134 135 Next 136 137 '// 此条件为真时表示没有找到 = ,则本次内容为无值属性 138 If iCount > BoundOfArray Then strTitle = StrConv(mulBits, vbUnicode) 139 140 With iopNode 141 142 ReDim Preserve .Attribs(.AttribCount) 143 144 .Attribs(.AttribCount).Name = strTitle 145 .Attribs(.AttribCount).Value = strValue 146 .AttribCount = .AttribCount + 1 147 148 End With 149 150 151 End Sub 152 153 Private Function m3u8_stream_load_node_value(mulBits() As Byte, iopNode As m3u8_node) As Long 154 155 '// 识别是否具有属性 156 '// 分析的数据示例: 157 '// #EXT-X-KEY:METHOD=AES-128,URI="https://askbfcdn.com/20241025/maEwILYb/2000kb/hls/key.key",IV=0x00000000000000000000000000000000 158 '// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 159 160 Dim BoundOfArray As Long 161 Dim iCount As Long 162 163 Dim flag_has_attribs As Boolean 164 165 Dim bytesAttr() As Byte 166 167 Dim char_last_pss As Long '// 上次 # 位置 + 1 168 ' Dim char_flag_pss As Byte '// 标识符 169 Dim char_now_pss As Long '// 本次 # 位置 - 1 170 Dim char_size_pss As Long '// 内容长度 171 172 BoundOfArray = UBound(mulBits) 173 174 For iCount = 0 To BoundOfArray 175 176 Select Case mulBits(iCount) 177 Case 44 ' "," 178 179 flag_has_attribs = True 180 181 ' char_now_pss = iCount - 1 182 183 bytesAttr = m3u8_stream_cutpart_clearnullchar(mulBits, char_last_pss, iCount - char_last_pss, 0) 184 185 m3u8_stream_load_node_attribs bytesAttr, iopNode 186 187 char_last_pss = iCount + 1 188 189 190 Case 34: mulBits(iCount) = 0 ' 双引号 " 191 192 Case 61: flag_has_attribs = True ' "=" '// ???????????? 暂未启用的条件 193 194 End Select 195 196 Next 197 198 '// 如果值,没有属性时如何识别,如果值只有一个属性时,怎样区分: #EXT-X-KEY:METHOD=AES-128, #EXT-X-KEY:NONE 的区别 199 200 If flag_has_attribs Then 201 202 bytesAttr = m3u8_stream_cutpart_clearnullchar(mulBits, char_last_pss, BoundOfArray - char_last_pss + 1, 0) 203 204 m3u8_stream_load_node_attribs bytesAttr, iopNode 205 206 Else 207 iopNode.Value = StrConv(mulBits, vbUnicode) 208 209 End If 210 211 End Function 212 213 Private Function m3u8_stream_load_node(mulBits() As Byte, iopNode As m3u8_node) As Long 214 215 '// 分析的数据示例: #EXT-X-MEDIA-SEQUENCE:0 216 '// #EXTINF:2.2,https://askbfcdn.com/20240926/FniAhBPw/1000kb/hls/6mscWE8g.ts 217 '// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 218 219 Debug.Print StrConv(mulBits, vbUnicode) 220 221 Dim BoundOfArray As Long 222 Dim iCount As Long 223 224 Dim byteOfTitle() As Byte 225 Dim strTitle As String 226 227 Dim byteOfValue() As Byte 228 Dim strValue As String 229 230 BoundOfArray = UBound(mulBits) 231 232 If mulBits(0) = 58 Then Exit Function '// #: 形式的视为无效条目, 冒号位置有问题 233 If mulBits(BoundOfArray) = 58 Then Exit Function '// #xxxx: 形式的视为无效条目,冒号位置有问题,有标签没有值为错误语法 234 235 For iCount = 0 To BoundOfArray 236 237 Select Case mulBits(iCount) 238 Case 58 ' ":" 239 240 241 If iCount < 4 Then MsgBox "不是节点标签" '// 小于 #EXT 的 4 字节长度, 视为注释 242 243 244 '// 检测冒号(:) 获取条目名 245 ReDim byteOfTitle(iCount - 1) 246 CopyMemory byteOfTitle(0), mulBits(0), iCount 247 248 ' Dim testFlags As Long 249 ' CopyMemory testFlags, byteOfTitle(0), 4 250 ' If testFlags <> &H54584523 Then MsgBox "属于注释行" '// 名字不符合规范, 不是 #EXT 开头的, 视为注释 251 252 '// 此时应检查 byteoftitle 前 4 个字节是否为 #EXT, &H54584523 253 254 255 '// 存储条目名 256 strTitle = StrConv(byteOfTitle, vbUnicode) 257 258 259 '// 提取节点的值的部分 260 ReDim byteOfValue(BoundOfArray - iCount - 1) 261 CopyMemory byteOfValue(0), mulBits(iCount + 1), BoundOfArray - iCount 262 263 strValue = StrConv(byteOfValue, vbUnicode) 264 265 266 267 '// 分析节点值的部分,获取到的结果放到 NewNode 268 Call m3u8_stream_load_node_value(byteOfValue, iopNode) 269 270 271 '// 272 m3u8_stream_load_node = 1 273 274 '// 第 1 个冒号后面的数据,全当做值处理,不再继续分析,后续数据已经过 属性分析 275 Exit For 276 277 End Select 278 279 Next 280 281 '// 存在没有值的独立节点条目 282 If iCount > BoundOfArray Then strTitle = StrConv(mulBits, vbUnicode): m3u8_stream_load_node = 1 283 284 iopNode.Title = strTitle 285 iopNode.Value = strValue 286 287 ''''''''' '// 对新的 node 条目节点, 数据归集 288 ''''''''' Select Case strTitle 289 ''''''''' Case m3u8_node_EXTM3U: PG.m3u8_format.SymbolBegin = strTitle 290 '''''''''' Case m3u8_node_EXT_X_ENDLIST 291 '''''''''' Case m3u8_node_EXT_X_KEY 292 ''''''''' Case m3u8_node_EXTINF: PG.m3u8_format.SymbolEnd = strTitle 293 ''''''''' Case Else 294 ''''''''' Me.Add strTitle, NewNode.Value 295 ''''''''' 296 ''''''''' 297 ''''''''' End Select 298 299 300 End Function 301 302 Private Function m3u8_stream_load(mulBits() As Byte) As Long 303 304 '// 解析 m3u8 文件数据, mulUnicodeBits = 字节数组, 该数组必须是 utf-8 类型, 函数会对数据核对,不符合标准要求的视为错误 305 '// 错误 = m3u8_stream_load ( utf8字节数组 ) 306 307 Dim NewNode As m3u8_node 308 309 Dim SizeOfArray As Long 310 Dim iCount As Long 311 312 Dim arrayNode() As Byte 313 314 Dim char_last_pss As Long '// 上次 # 位置 + 1 315 Dim char_flag_pss As Byte '// 标识符 316 ' Dim char_now_pss As Long '// 本次 # 位置 - 1 317 Dim char_size_pss As Long '// 内容长度 318 Dim char_null_pss As Long '// 空字符 319 320 '// 双引号三态标志, 用于在检测 # 号时, 如果存在被双引号包括的 #,应该被识别为一个文本 321 ' Dim flags_doubleQuotation As flags_three_states 322 Dim flags_doubleQuotation As Boolean 323 324 SizeOfArray = UBound(mulBits) 325 326 327 '// 数据整理, 将 CR、LF、0 to &H1F、&H7F To &H9F ,全部删除 328 '// 整理后的数据没有分行,数据都位于 # 开头的节点之内 329 '// 然后进行 行数据 分析 330 331 For iCount = 0 To SizeOfArray 332 333 Select Case mulBits(iCount) 334 Case 35 ' "#" 335 336 '// 检测双引号状态,当处于不配对双引号状态时, 说明此时 # 处于文本描述过程中 337 If (flags_doubleQuotation = False) Then 338 339 '// 设置字节标志 340 char_flag_pss = 35 341 342 '// 如果未设置过复制起始位置 343 If char_last_pss = 0 Then 344 345 ' // 设置起始点 346 char_last_pss = iCount + 1 347 348 Else 349 350 LastLoop: 351 arrayNode = m3u8_stream_cutpart_clearnullchar(mulBits, char_last_pss, iCount - char_last_pss, char_flag_pss) 352 353 '// 将获取的有效数据文本 提交处理 354 355 '// 此时取得的结果为: #EXT-X-MEDIA-SEQUENCE:0 356 '// #EXTINF:2.2,https://askbfcdn.com/20240926/FniAhBPw/1000kb/hls/6mscWE8g.ts 357 358 359 If m3u8_stream_load_node(arrayNode, NewNode) Then 360 '// 361 362 Dim a As Long 363 Dim n As String 364 If NewNode.AttribCount Then 365 For a = 0 To NewNode.AttribCount - 1 366 n = n & "{" & a & "} [" & NewNode.Attribs(a).Name & "]" & " <> " & "[" & NewNode.Attribs(a).Value & "]" & vbCrLf 367 Next 368 End If 369 370 SendMessage Form1.Text2.hWnd, EM_SETSEL, ByVal Len(Form1.Text2), ByVal Len(Form1.Text2) 371 372 373 ' Form1.Text2.SelStart = Len(Form1.Text2) 374 ' Form1.Text2.SelLength = 1 375 Form1.Text2.SelText = "<" & NewNode.Title & ">" & _ 376 "<" & NewNode.Value & ">" & _ 377 " arrribs=" & NewNode.AttribCount & vbCrLf & _ 378 n & vbCrLf 379 380 Erase NewNode.Attribs 381 NewNode.AttribCount = 0 382 n = "" 383 ' NewNode.Title 384 385 386 Else 387 '// raise error 388 389 390 391 End If 392 393 '// 以上分析完成后, 从新设置新的复制点 394 char_last_pss = iCount + 1 395 char_flag_pss = 35 396 397 End If 398 399 End If 400 401 Case 34: flags_doubleQuotation = Not flags_doubleQuotation '// 双引号配对状态检测, =true不配对 'flags_three_states_Transformation flags_doubleQuotation '// 三态变换 402 403 404 ' Case 58 ' ":" 冒号 405 ' 406 ' Case 59 ' ";" 分号 407 408 Case 10, 13: mulBits(iCount) = 0 '// 把 cr/lf 字符转换为空字符,在提取数据前将空字符排除 409 410 Case 1 To &H1F, &H7F To &H9F: mulBits(iCount) = 0 '// 在 m3u8 定义中,这些都属于不应该出现的字符 411 '// 禁止出现的字符 412 413 Case Else: If iCount = SizeOfArray Then GoTo LastLoop '// 最后一段字符时 414 415 End Select 416 417 418 419 Next 420 421 422 423 424 425 426 End Function