MP3 validieren

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 6 Antworten in diesem Thema. Der letzte Beitrag () ist von slice.

    woeh schrieb:

    habe die lösung gefunden


    Freut mich für dich!!

    Aber es wird sicherlich auch andere User interessieren - mich mit eingeschlossen.
    Könntest du die von dir selbst erstellte Lösung für dein Problem bitte posten und ggf. ein paar Worte dazu sagen?

    Danke!


    VG Acr0most
    Wenn das Leben wirklich nur aus Nullen und Einsen besteht, dann laufen sicherlich genügen Nullen frei herum. :D
    Signature-Move 8o
    kein Problem mit privaten Konversationen zu Thema XY :thumbup:
    also....ich bin ein vb.net anfänger und komme von vb6.
    ich code z.zt ein prog, das ich schon immer haben wollte und das es nicht gibt.

    die lösung habe ich in 2 alten klassen von vb6 gefunden.
    naja....die meiste arbeit hat VBuc 7.2 erledigt.
    ich habe dann nur die letzten fehler ausgemerzt und die 2 klassen kombiniert.
    dazu gibts dann natürlich auch alle propertys der mp3.....

    würde mich über fehler oder dergleichen und rückmeldung freuen ;)

    aufruf aus form1

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    3. Dim clsID3 As New clsMP3Header
    4. clsID3.FilePath = "C:\MeineMP3.mp3"
    5. If clsID3.GetFileInfos() <> -1 Then
    6. MsgBox("Valid !")
    7. End If
    8. End Sub
    9. End Class


    die klasse selber

    Quellcode

    1. Option Strict Off
    2. Option Explicit On
    3. Imports Microsoft.VisualBasic
    4. Imports System
    5. Friend Class clsMP3Header
    6. Private anBitrateLookup(7, 15) As Integer
    7. Private alFreqLookup(3, 7) As Integer
    8. Private avFrameRates(3) As Double
    9. Private m_sFilePath As String = ""
    10. Private m_lFileSize As Integer
    11. Private m_bytVersion As Byte
    12. Private m_bytLayer As Byte
    13. Private m_bCRCProtected As Boolean
    14. Private m_lBitrate As Integer
    15. Private m_lFrequency As Integer
    16. Private m_bPadding As Boolean
    17. Private m_bPrivateBit As Boolean
    18. Private m_bytChannelMode As Byte
    19. Private m_bytChannelModeExtention As Byte
    20. Private m_bCopyright As Boolean
    21. Private m_bOriginal As Boolean
    22. Private m_bytEmphasis As Byte
    23. Private m_sVersionText As String = ""
    24. Private m_sLayerText As String = ""
    25. Private m_sChannelModeText As String = ""
    26. Private m_sEmphasisText As String = ""
    27. Private m_lFrameSize As Integer
    28. Private m_lFrames As Integer
    29. Private m_lSeconds As Integer
    30. Private m_ID3V2 As Boolean
    31. Private m_ID3V1 As Boolean
    32. Private m_HeaderPosition As Integer
    33. Public Property FilePath() As String
    34. Get
    35. Return m_sFilePath
    36. End Get
    37. Set(ByVal Value As String)
    38. ZeroValues()
    39. m_sFilePath = Value
    40. End Set
    41. End Property
    42. Public ReadOnly Property Minutes() As Double
    43. Get
    44. Return Math.Round((m_lSeconds / 60), 2)
    45. End Get
    46. End Property
    47. Public ReadOnly Property FileSize() As String
    48. Get
    49. Return CStr(m_lFileSize)
    50. End Get
    51. End Property
    52. Public ReadOnly Property Version() As Byte
    53. Get
    54. Return m_bytVersion
    55. End Get
    56. End Property
    57. Public ReadOnly Property Layer() As Byte
    58. Get
    59. Return m_bytLayer
    60. End Get
    61. End Property
    62. Public ReadOnly Property CRCProtected() As Boolean
    63. Get
    64. Return m_bCRCProtected
    65. End Get
    66. End Property
    67. Public ReadOnly Property Bitrate() As Integer
    68. Get
    69. Return m_lBitrate / 1000
    70. End Get
    71. End Property
    72. Public ReadOnly Property Frequency() As Integer
    73. Get
    74. Return m_lFrequency
    75. End Get
    76. End Property
    77. Public ReadOnly Property Padding() As Boolean
    78. Get
    79. Return m_bPadding
    80. End Get
    81. End Property
    82. Public ReadOnly Property PrivateBit() As Boolean
    83. Get
    84. Return m_bPrivateBit
    85. End Get
    86. End Property
    87. Public ReadOnly Property Channelmode() As Byte
    88. Get
    89. Return m_bytChannelMode
    90. End Get
    91. End Property
    92. Public ReadOnly Property ChannelModeExtention() As Byte
    93. Get
    94. Return m_bytChannelModeExtention
    95. End Get
    96. End Property
    97. Public ReadOnly Property Copyright() As Boolean
    98. Get
    99. Return m_bCopyright
    100. End Get
    101. End Property
    102. Public ReadOnly Property Original() As Boolean
    103. Get
    104. Return m_bOriginal
    105. End Get
    106. End Property
    107. Public ReadOnly Property Emphasis() As Byte
    108. Get
    109. Return m_bytEmphasis
    110. End Get
    111. End Property
    112. Public ReadOnly Property VersionText() As String
    113. Get
    114. Return m_sVersionText
    115. End Get
    116. End Property
    117. Public ReadOnly Property LayerText() As String
    118. Get
    119. Return m_sLayerText
    120. End Get
    121. End Property
    122. Public ReadOnly Property ChannelModeText() As String
    123. Get
    124. Return m_sChannelModeText
    125. End Get
    126. End Property
    127. Public ReadOnly Property EmphasisText() As String
    128. Get
    129. Return m_sEmphasisText
    130. End Get
    131. End Property
    132. Public ReadOnly Property FrameSize() As Integer
    133. Get
    134. Return m_lFrameSize
    135. End Get
    136. End Property
    137. Public ReadOnly Property Frames() As Integer
    138. Get
    139. Return m_lFrames
    140. End Get
    141. End Property
    142. Public ReadOnly Property Seconds() As Integer
    143. Get
    144. Return m_lSeconds
    145. End Get
    146. End Property
    147. Public ReadOnly Property HeaderPosition() As Integer
    148. Get
    149. Return m_HeaderPosition
    150. End Get
    151. End Property
    152. Public ReadOnly Property ID3V1() As Boolean
    153. Get
    154. Return m_ID3V1
    155. End Get
    156. End Property
    157. Public ReadOnly Property ID3V2() As Boolean
    158. Get
    159. Return m_ID3V2
    160. End Get
    161. End Property
    162. Public Sub New()
    163. MyBase.New()
    164. Dim sBitrateData As String = "999,999,999,999,999,999," &
    165. "032,032,032,032,008,008," &
    166. "064,048,040,048,016,016," &
    167. "096,056,048,056,024,024," &
    168. "128,064,056,064,032,032," &
    169. "160,080,064,080,040,040," &
    170. "192,096,080,096,048,048," &
    171. "224,112,096,112,056,056," &
    172. "256,128,112,128,064,064," &
    173. "288,160,128,144,080,080," &
    174. "320,192,160,160,096,096," &
    175. "352,224,192,176,112,112," &
    176. "384,256,224,192,128,128," &
    177. "416,320,256,224,144,144," &
    178. "448,384,320,256,160,160," &
    179. "999,999,999,999,999,999"
    180. Dim asBitrateCore() As String = sBitrateData.Split(","c)
    181. For nBitRate As Integer = 1 To 14
    182. For nVerLayer As Integer = 0 To 2
    183. anBitrateLookup(7 - nVerLayer, nBitRate) = Conversion.Val(asBitrateCore((nBitRate * 6) + nVerLayer))
    184. Next
    185. For nVerLayer As Integer = 0 To 2
    186. anBitrateLookup(3 - nVerLayer, nBitRate) = Conversion.Val(asBitrateCore((nBitRate * 6) + 3 + nVerLayer))
    187. Next
    188. Next
    189. Dim sFreqData As String = "44100,22050,11025," &
    190. "48000,24000,12000," &
    191. "32000,16000,08000," &
    192. "99999,99999,99999"
    193. Dim asFreqCore() As String = sFreqData.Split(","c)
    194. For nFreq As Integer = 0 To 3
    195. alFreqLookup(3, nFreq) = CInt(Conversion.Val(asFreqCore(nFreq * 3)))
    196. alFreqLookup(2, nFreq) = CInt(Conversion.Val(asFreqCore((nFreq * 3) + 1)))
    197. alFreqLookup(0, nFreq) = CInt(Conversion.Val(asFreqCore((nFreq * 3) + 2)))
    198. Next
    199. avFrameRates(0) = 38.5
    200. avFrameRates(1) = 32.5
    201. avFrameRates(2) = 27.8
    202. avFrameRates(3) = 0
    203. End Sub
    204. Private Sub ZeroValues()
    205. m_lFileSize = 0
    206. m_bytVersion = 0
    207. m_bytLayer = 0
    208. m_bCRCProtected = False
    209. m_lBitrate = 0
    210. m_lFrequency = 0
    211. m_bPadding = False
    212. m_bPrivateBit = False
    213. m_bytChannelMode = 0
    214. m_bytChannelModeExtention = 0
    215. m_bCopyright = False
    216. m_bOriginal = False
    217. m_bytEmphasis = 0
    218. m_sVersionText = ""
    219. m_sLayerText = ""
    220. m_sChannelModeText = ""
    221. m_sEmphasisText = ""
    222. m_lFrameSize = 0
    223. m_lFrames = 0
    224. m_lSeconds = 0
    225. End Sub
    226. Public Function GetFileInfos() As Integer
    227. Dim result As Integer = 0
    228. Dim FileExist() As Integer
    229. Dim sMP3bitsString As String = ""
    230. Dim nBit1, nBit2, nBitD1, nBitD2 As Integer
    231. Dim dSHIFT As String = ""
    232. Dim FrameSize As Double
    233. Dim aBytes(3) As Byte
    234. Dim ID3V2Len As Double
    235. Dim sID3Len As String = ""
    236. Dim ID3Position, HDPos As Integer
    237. 'On Local Error Resume Next
    238. On Error GoTo Quit
    239. m_ID3V2 = False
    240. result = -1
    241. If Not IO.File.Exists(m_sFilePath) Then Return result
    242. If ID3v2GetFrameSize(m_sFilePath, ID3V2Len) = False Then
    243. Return -1
    244. End If
    245. result = 0
    246. Dim nFile As Integer = FileSystem.FreeFile()
    247. FileSystem.FileOpen(nFile, m_sFilePath, OpenMode.Binary)
    248. FileSystem.Seek(nFile, FileSystem.LOF(nFile) - 127)
    249. Dim sInput As String = FileSystem.InputString(nFile, 128)
    250. If sInput.StartsWith("TAG") Then
    251. m_ID3V1 = True
    252. End If
    253. FileSystem.Seek(nFile, 1)
    254. 'Einlesen der ersten vier Kilobytes um
    255. 'den Header der Datei zu finden
    256. sInput = FileSystem.InputString(nFile, 8192)
    257. 'Wird für die Berechnung der Trackduration benötigt
    258. m_lFileSize = FileSystem.LOF(nFile)
    259. ' Ist ein ID3V2-Tag vorhanden?
    260. If sInput.StartsWith("ID3") Then
    261. ID3Position = 1
    262. m_ID3V2 = True
    263. End If
    264. If ID3Position Then
    265. ' Bytes mit Längen-Info des Tags lesen
    266. sID3Len = sInput.Substring(ID3Position + 5, Math.Min(4, sInput.Length - (ID3Position + 5)))
    267. ' länge des Tags berechnen
    268. 'On Error Resume Next
    269. 'Call ID3v2RemoveTag(m_sFilePath,,, ID3V2Len, False)
    270. 'ID3V2Len = &H200000 * Strings.Asc(sID3Len.Substring(0, Math.Min(1, sID3Len.Length))(0)) + &H4000S * Strings.Asc(sID3Len.Substring(1, Math.Min(1, sID3Len.Length - 1))(0)) + &H80S * Strings.Asc(sID3Len.Substring(2, Math.Min(1, sID3Len.Length - 2))(0)) + Strings.Asc(sID3Len.Substring(3, Math.Min(1, sID3Len.Length - 3))(0))
    271. ' Tag überspringen
    272. FileSystem.Seek(nFile, CInt(ID3Position + ID3V2Len + 10))
    273. 'wird benötigt zur Berechnung der Headerposition
    274. HDPos = CInt(ID3Position + ID3V2Len + 10)
    275. m_lFileSize = CInt(m_lFileSize - (ID3Position + ID3V2Len + 10))
    276. ' neuen Einlesen
    277. sInput = FileSystem.InputString(nFile, 8192)
    278. End If
    279. FileSystem.FileClose(nFile)
    280. Dim i As Integer = 0
    281. Do Until i = 8191
    282. ReEnter:
    283. i += 1
    284. nBit1 = Strings.Asc(sInput.Substring(i - 1, Math.Min(1, sInput.Length - (i - 1)))(0))
    285. nBit2 = Strings.Asc(sInput.Substring(i, Math.Min(1, sInput.Length - i))(0))
    286. If nBit1 = &HFFS And (nBit2 And &HE0S) = &HE0S Then
    287. '20 HeadersBits auslesen - es sind die
    288. 'letzen 20 Bits der nexten 3 Bytes
    289. sMP3bitsString = sInput.Substring(i, Math.Min(3, sInput.Length - i))
    290. m_HeaderPosition = HDPos + i - 1
    291. Exit Do
    292. End If
    293. 'Wir haben die Sync nicht gefunden, deshalb
    294. 'verschieben wir das ganze um 4Bits nach links
    295. dSHIFT = ShiftBits(sInput.Substring(i - 1, Math.Min(3, sInput.Length - (i - 1))))
    296. nBitD1 = Strings.Asc(dSHIFT.Substring(0, Math.Min(1, dSHIFT.Length))(0))
    297. nBitD2 = Strings.Asc(dSHIFT.Substring(Math.Max(dSHIFT.Length - 1, 0))(0))
    298. If nBitD1 = &HFFS And (nBitD2 And &HE0S) = &HE0S Then
    299. '20 HeaderBits auslesen - es sind die
    300. 'ersten 20 Bits der nexten 3 Bytes
    301. sMP3bitsString = sInput.Substring(i + 1, Math.Min(3, sInput.Length - (i + 1)))
    302. m_HeaderPosition = HDPos + i - 1
    303. Exit Do
    304. End If
    305. Loop
    306. If i = 8191 Then Return result 'Header wurde nicht gefunden!
    307. ' -> beenden der Routine
    308. For z As Integer = 1 To 3
    309. aBytes(z) = Strings.Asc(sMP3bitsString.Substring(z - 1)(0))
    310. Next
    311. 'Die ersten 20 Bits von sMP3bitsString sind die
    312. 'Headerinformationen für diesen Frame
    313. '1te Bit: ID | 0 = MPEG-2 | 1 = MPEG-1
    314. m_bytVersion = (&H18S And aBytes(1)) / 8
    315. Dim mp3_ID1 As Integer = (m_bytVersion And 1)
    316. 'folgende 2 Bits sind der Layer
    317. m_bytLayer = (&H6S And aBytes(1)) / 2
    318. 'folgendes Bit ist Protection
    319. Dim mp3_protection As Integer = &H1S And aBytes(1)
    320. m_bCRCProtected = mp3_protection <> 0
    321. 'folgende 4 Bits sind die Bitrate
    322. Dim mp3_bitrate As Integer = (&HF0S And aBytes(2)) / 16
    323. Dim LayerType As Integer = (mp3_ID1 * 4) Or m_bytLayer
    324. m_lBitrate = 1000 * (anBitrateLookup(LayerType, mp3_bitrate))
    325. 'folgende 2 Bits sind die Frequenz
    326. Dim mp3_frequency As Integer = (&HCS And aBytes(2)) / 4
    327. m_lFrequency = alFreqLookup(m_bytVersion, mp3_frequency)
    328. If m_lFrequency = 99999 Or m_lFrequency = 0 Or m_lBitrate = 0 Then
    329. i += 4
    330. GoTo ReEnter
    331. End If
    332. 'folgendes Bit ist das Padding Bit
    333. m_bPadding = ((&H2S And aBytes(2)) / 2) = 1
    334. 'folgendes Bit ist das Private Bit
    335. m_bPrivateBit = ((&H10S And aBytes(3)) / 2) = 1
    336. 'folgende 2 Bit sind der Channel mode
    337. m_bytChannelMode = (&HC0S And aBytes(3)) / 64
    338. 'folgende 2 Bits sind die Channel Mode Extention
    339. m_bytChannelModeExtention = (&H30S And aBytes(3)) / 16
    340. 'folgendes Bit ist der Copyright Flag
    341. m_bCopyright = ((&H8S And aBytes(3)) / 8) = 1
    342. 'folgendes Bit ist das Original Flag
    343. m_bOriginal = ((&H4S And aBytes(3)) / 4) = 1
    344. 'folgendes Bit ist das Emphasis Flag
    345. m_bytEmphasis = &H3S And aBytes(3)
    346. Select Case m_bytVersion
    347. Case 0
    348. m_sVersionText = "MPEG-2.5"
    349. Case 1
    350. Case 2
    351. m_sVersionText = "MPEG-2.0"
    352. Case 3
    353. m_sVersionText = "MPEG-1.0"
    354. End Select
    355. Select Case m_bytLayer
    356. Case 1
    357. m_sLayerText = "Layer III"
    358. FrameSize = (144 * (m_lBitrate / m_lFrequency))
    359. Case 2
    360. m_sLayerText = "Layer II"
    361. FrameSize = (144 * (m_lBitrate / m_lFrequency))
    362. Case 3
    363. m_sLayerText = "Layer I"
    364. FrameSize = (12 * (m_lBitrate / m_lFrequency) + Math.Abs(CInt(m_bPadding))) * 4
    365. End Select
    366. Select Case m_bytChannelMode
    367. Case 0
    368. m_sChannelModeText = "Stereo"
    369. Case 1
    370. m_sChannelModeText = "Joint Stereo"
    371. If m_bytVersion < 3 Then FrameSize = IIf(FrameSize > 0, Math.Floor(FrameSize), Math.Ceiling(FrameSize)) / 2
    372. If m_bytVersion = 0 Then FrameSize = IIf(FrameSize > 0, Math.Floor(FrameSize), Math.Ceiling(FrameSize)) / 2
    373. Case 2
    374. m_sChannelModeText = "Dual Channel"
    375. Case 3
    376. m_sChannelModeText = "Single Channel"
    377. If m_bytVersion < 3 Then FrameSize = IIf(FrameSize > 0, Math.Floor(FrameSize), Math.Ceiling(FrameSize)) / 2
    378. End Select
    379. Select Case m_bytEmphasis
    380. Case 0
    381. m_sEmphasisText = "None"
    382. Case 1
    383. m_sEmphasisText = "50/15 ms"
    384. Case 2
    385. m_sEmphasisText = "Reserved"
    386. Case 3
    387. m_sEmphasisText = "CIT J.17"
    388. End Select
    389. 'Ausrechnen der Frameanzahl und der Spieldauer
    390. m_lFrameSize = CInt(IIf(FrameSize > 0, Math.Floor(FrameSize), Math.Ceiling(FrameSize)))
    391. m_lFrames = CInt(m_lFileSize / IIf(FrameSize > 0, Math.Floor(FrameSize), Math.Ceiling(FrameSize)))
    392. m_lSeconds = CInt(m_lFrames / avFrameRates(mp3_frequency))
    393. m_lSeconds = m_lSeconds
    394. Return result
    395. Quit:
    396. If Err.Number <> 0 Then
    397. Err.Clear()
    398. Return -1
    399. End If
    400. End Function
    401. Private Function ShiftBits(ByRef sInput As String) As String
    402. Dim nSD1 As Integer = Strings.Asc(sInput.Substring(0, Math.Min(1, sInput.Length))(0))
    403. Dim nSD2 As Integer = Strings.Asc(sInput.Substring(1, Math.Min(1, sInput.Length - 1))(0))
    404. Dim nSD3 As Integer = Strings.Asc(sInput.Substring(Math.Max(sInput.Length - 1, 0))(0))
    405. Dim nDO1 As Integer = ((nSD1 And &HFS) * 16) Or ((nSD2 And &HF0S) / 16)
    406. Dim nDO2 As Integer = ((nSD2 And &HFS) * 16) Or ((nSD3 And &HF0S) / 16)
    407. Return Strings.Chr(nDO1).ToString() & Strings.Chr(nDO2).ToString()
    408. End Function
    409. Public Function ID3v2GetFrameSize(ByVal file As String,
    410. ByRef frameSize As Long,
    411. Optional lb As ListBox = Nothing) _
    412. As Boolean
    413. Dim tmpFile As String
    414. Dim bytTemp() As Byte, bytByte() As Byte, byt() As Byte
    415. Dim lngSizeLeft, lngTotalSize
    416. Dim result As Boolean
    417. On Error GoTo Quit
    418. Dim fsr As IO.FileStream
    419. Dim reader As IO.BinaryReader
    420. fsr = New IO.FileStream(file, IO.FileMode.Open, IO.FileAccess.Read)
    421. reader = New IO.BinaryReader(fsr)
    422. Call reader.ReadBytes(3)
    423. ReDim bytTemp(6)
    424. bytTemp = reader.ReadBytes(bytTemp.Length)
    425. frameSize = LeftShift(bytTemp(3), 21) + LeftShift(bytTemp(4), 14) + LeftShift(bytTemp(5), 7) + bytTemp(6) + 10
    426. result = True
    427. Quit:
    428. If Err.Number <> 0 Then
    429. frameSize = -1
    430. result = False
    431. End If
    432. reader.Close()
    433. fsr.Close()
    434. reader.Dispose()
    435. fsr.Dispose()
    436. Err.Clear()
    437. Return result
    438. End Function
    439. Private Function LeftShift(ByVal intNumber As Integer, ByVal bytAmount As Byte) As Integer
    440. Dim strTemp As String = ""
    441. If bytAmount = 0 Then
    442. Return intNumber
    443. Else
    444. strTemp = NumberToBinary(intNumber)
    445. strTemp = strTemp & New String("0", bytAmount)
    446. Return BinaryToLong(strTemp)
    447. End If
    448. End Function
    449. Private Function BinaryToLong(ByVal strBinary As String) As Integer
    450. Dim lngTotal As Integer
    451. Dim bytBit As Byte
    452. Dim lngFactor As Integer = 0
    453. For lngCounter As Integer = Len(strBinary) To 1 Step -1
    454. bytBit = CByte(strBinary.Substring(lngCounter - 1, Math.Min(1, strBinary.Length - (lngCounter - 1))))
    455. lngTotal += (bytBit * (2 ^ lngFactor))
    456. lngFactor += 1
    457. Next
    458. Return lngTotal
    459. End Function
    460. Private Function NumberToBinary(ByVal intNumber As Integer) As String
    461. Dim result As String = ""
    462. Dim lngNumber As Integer
    463. Dim strTemp, strPad As String
    464. lngNumber = intNumber
    465. Do
    466. If (lngNumber Mod 2) = 0 Then
    467. strTemp = "0" & strTemp
    468. Else
    469. strTemp = "1" & strTemp
    470. End If
    471. lngNumber = lngNumber \ 2
    472. Application.DoEvents()
    473. Loop Until lngNumber = 0
    474. If Len(strTemp) < 8 Then
    475. strPad = New String("0", 8 - Strings.Len(strTemp))
    476. End If
    477. Return strPad & strTemp
    478. End Function
    479. End Class

    woeh schrieb:

    habe die lösung gefunden
    Leider nicht :!:
    Ich hab das ganze mit ner Excel-Datei aufgerufen, die ist angeblich auch valid! ;(
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    naja...wenn der code an bestimmten stellen die bytes findet, liest er natürlich den rest aus...
    ist trotzdem ne klasse klasse ,) für meine bedürfnisse reicht es...

    wenn ich das richtig verstanden habe, müßte man checken, ob sich in der datei ein valieder audiostream befindet...ich glaube nur so gehts.....

    danke für die rückmeldung ;)
    @woeh Vielleicht rufst Du einen Audioplayer auf, der weiß es ganz genau.
    Wie kommen denn da Nicht-MP3-Dateien rein?
    Genügt es nicht, einfach die Extension zu testen?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!