Un4seen.Bass.Misc Bild zu MP3 Tags hinzufügen

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

Es gibt 12 Antworten in diesem Thema. Der letzte Beitrag () ist von -Franky-.

    Un4seen.Bass.Misc Bild zu MP3 Tags hinzufügen

    Hat schon mal jemand ein Bild zu den Tags hinzugefügt?
    Bei mir klappt das einfach nicht.

    VB.NET-Quellcode

    1. Dim tag As New TAG_INFO()
    2. tag.tagType = BASSTag.BASS_TAG_ID3V2
    3. tag.title = "Titel"
    4. tag.artist = "Artist"
    5. tag.genre = "Genre"
    6. Dim pic = New TagPicture("d:/mypic.jpg")
    7. tag.AddPicture(pic)
    8. LameEncoder.TAGs = tag

    Alle Tags werden in der anschließend erzeugten MP3 angelegt, aber das Bild bleibt leer.
    Klicke ich die MP3 Datei m Exploerer an, werden alle Tags angezeigt, aber kein Bild.

    Ich habe auch u.A. folgendes probiert:

    VB.NET-Quellcode

    1. Dim img As Image = Image.FromFile("d:/mypic.jpg")
    2. Dim pic = New TagPicture(img, TagPicture.PICTURE_TYPE.FrontAlbumCover, "")
    3. tag.AddPicture(pic)

    Aber auch ohne Erfolg.

    *Topic verschoben*

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Marcus Gräfe“ ()

    roepke schrieb:

    Hat schon mal jemand ein Bild zu den Tags hinzugefügt?

    Jupp, hab ich. Allerdings nicht mit der Bass. Mit der Bass kenne ich mich auch nicht aus. Die Frage wäre, wird das Bild tatsächlich nicht hinzugefügt? Die MP3 müsste ja entsprechend größer werden. Wenn doch, ist der Tag auch ID3v2 konform wie in id3.org/id3v2.3.0 beschrieben -> 4.15. Attached picture ?

    Ansonsten kenne ich halt eine Möglichkeit per COM-Interface IPropertyStore ein Bild zu einer MP3 hinzuzufügen. Allerdings mit dem Nachteil das man nicht angeben kann, das es sich um ein FrontCover oder oder handelt. Zumindest zeigt der Explorer dieses Bild aber an.
    Mfg -Franky-
    Also die BASS kann nur Tags lesen. Siehe hier:
    bass.radio42.com/help/html/c25…22d-b7b5-4bf264137901.htm

    Nur BASS_TAG_GetFromFile, BASS_TAG_GetFromURL, sowie ReadID3v1 und 2.

    Will man das über ein StreamHandle machen geht es auch nicht, da gibt es auch nur BASS_ChannelGetTags.

    Ich hab auch schon diverse Tags geschrieben. Geht los mit EXIF, ID3 (bis 2.4), MonkeyAudio(APE) und Matroska(nur lesend). Kann man mit ein wenig Mühe leicht alles selbst schreiben. Gibt aber auch fertiges Bibliotheken auf Github oder auch Nuget Packete.

    Edit:
    @roepke
    Warte mal, so kann die bass die tags wirklich schreiben? Verstehe nicht, warum das nicht auch bei bereits bestehenden Dateien geht. Du scheint ja was aufzunehmen, wie genau? Nutzt du FFMPEG mit der BASS zusammen?

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „BitBrösel“ ()

    Danke für die Antworten.

    @-Franky- hättest Du ein konkretes Beispiel

    Also die BASS kann nur Tags lesen.

    Sicher?
    bass.radio42.com/help/html/7e1…54b-77d7-f0d08466284c.htm
    Weil mit

    VB.NET-Quellcode

    1. Private LameEncoder As EncoderLAME 'Class Un4seen.Bass.Misc.EncoderLAME
    2. Private Sub btn_Start_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_Start.Click
    3. LameEncoder = New EncoderLAME(Dummy)
    4. LameEncoder.LAME_UseVBR = False
    5. LameEncoder.LAME_Bitrate = EncoderLAME.BITRATE.kbps_320
    6. LameEncoder.LAME_TargetSampleRate = EncoderLAME.SAMPLERATE.Hz_48000
    7. LameEncoder.LAME_Mode = EncoderLAME.LAMEMode.Stereo
    8. Dim tag As New TAG_INFO()
    9. tag.tagType = BASSTag.BASS_TAG_ID3V2
    10. tag.title = "Titel"
    11. tag.artist = "Artist"
    12. tag.genre = "Genre"
    13. LameEncoder.TAGs = tag
    14. LameEncoder.OutputFile = "MyFile.mp3"
    15. LameEncoder.Start(Nothing, IntPtr.Zero, False)
    16. End Sub

    Wird die MP3 erzeugt und die Tags .tagType, .title, .artist, .genre gesetzt.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „roepke“ ()

    roepke schrieb:

    Sicher?


    Zumindest bei bestehenden Dateien. Da du konvertierst musst du AFAIK auch FFMPEG nutzen, ich meine mich zu erinnern das das nur damit geht, könnte sein das da das Problem liegt. Ich schau im Laufe des Tages mal ob man was machen kann. Hast du mal mit einem Tool geschaut ob das Bild wirklich nicht drin ist? Könnte sein das Windows das nicht aktualisiert hat.

    roepke schrieb:

    @-Franky- hättest Du ein konkretes Beispiel

    Für .NET derzeit nicht. Nur für VB6. Spielt aber eigentlich keine Rolle da das ganze COM basiert ist. Allerdings geht das da auch nur bei bestehenden Dateien. Im Prinzip erstellst Dir ein COM-Interface IPropertyStore über die API SHGetPropertyStoreFromParsingName mit den Flags GPS_READWRITE Or GPS_HANDLERPROPERTIESONLY von der Datei. Per IPropertyStore.SetValue schreibst dann dann das Bild, das zuvor in ein COM-Interface IStream überführt wurde (zb per CreateStreamOnHGlobal, besser per SHCreateMemStream), als VT_STREAM mit System.ThumbnailStream (PROPERTYKEY) in die MP3 und am Ende das ganze mit IPropertyStore.Commit finalisieren.

    Edit: Auf die schnelle für .NET. Beim löschen wird zwar das Bild aus der MP3 gelöscht, aber der verwendete Platz in der MP3 nicht freigegeben! Desweiteren lassen sich über den IPropertyStore auch alle anderen MP3-Tags auslesen und teilseise auch schreiben und den IPropertyStore kann man auf alle möglichen Dateien anwenden (zB Officedokumente, Bilder, Video usw)
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Option Strict On
    2. Option Explicit On
    3. Imports System.Runtime.InteropServices
    4. Public Class Form1
    5. Private Const S_OK As Integer = &H0
    6. Private Const IID_IPropertyStore As String = "886d8eeb-8cf2-4446-8d02-cdba1dbdcf99"
    7. Private Enum GETPROPERTYSTOREFLAGS As Integer
    8. GPS_DEFAULT = &H0
    9. GPS_HANDLERPROPERTIESONLY = &H1
    10. GPS_READWRITE = &H2
    11. GPS_TEMPORARY = &H4
    12. GPS_FASTPROPERTIESONLY = &H8
    13. GPS_OPENSLOWITEM = &H10
    14. GPS_DELAYCREATION = &H20
    15. GPS_BESTEFFORT = &H40
    16. GPS_NO_OPLOCK = &H80
    17. GPS_PREFERQUERYPROPERTIES = &H100
    18. GPS_EXTRINSICPROPERTIES = &H200
    19. GPS_EXTRINSICPROPERTIESONLY = &H400
    20. GPS_VOLATILEPROPERTIES = &H800
    21. GPS_VOLATILEPROPERTIESONLY = &H1000
    22. GPS_MASK_VALID = &H1FFF
    23. End Enum
    24. Private Enum VARTYPE As UShort
    25. VT_EMPTY = 0
    26. VT_NULL = 1
    27. VT_I2 = 2
    28. VT_I4 = 3
    29. VT_R4 = 4
    30. VT_R8 = 5
    31. VT_CY = 6
    32. VT_DATE = 7
    33. VT_BSTR = 8
    34. VT_DISPATCH = 9
    35. VT_ERROR = 10
    36. VT_BOOL = 11
    37. VT_VARIANT = 12
    38. VT_UNKNOWN = 13
    39. VT_DECIMAL = 14
    40. VT_I1 = 16
    41. VT_UI1 = 17
    42. VT_UI2 = 18
    43. VT_UI4 = 19
    44. VT_I8 = 20
    45. VT_UI8 = 21
    46. VT_INT = 22
    47. VT_UINT = 23
    48. VT_VOID = 24
    49. VT_HRESULT = 25
    50. VT_PTR = 26
    51. VT_SAFEARRAY = 27
    52. VT_CARRAY = 28
    53. VT_USERDEFINED = 29
    54. VT_LPSTR = 30
    55. VT_LPWSTR = 31
    56. VT_RECORD = 36
    57. VT_FILETIME = 64
    58. VT_BLOB = 65
    59. VT_STREAM = 66
    60. VT_STORAGE = 67
    61. VT_STREAMED_OBJECT = 68
    62. VT_STORED_OBJECT = 69
    63. VT_BLOB_OBJECT = 70
    64. VT_CF = 71
    65. VT_CLSID = 72
    66. VT_ILLEGALMASKED = 4095
    67. VT_VECTOR = 4096
    68. VT_ARRAY = 8192
    69. VT_BYREF = 16384
    70. End Enum
    71. Private Structure PROPERTYKEY
    72. Dim fmtid As Guid
    73. Dim pid As Integer
    74. End Structure
    75. Private Structure CArray
    76. Dim cElems As UInteger
    77. Dim pElems As IntPtr
    78. End Structure
    79. <StructLayout(LayoutKind.Explicit, Size:=16)>
    80. Private Structure PROPVARIANT
    81. <FieldOffset(0)> Dim vt As VARTYPE
    82. <FieldOffset(2)> Dim wReserved1 As UShort
    83. <FieldOffset(4)> Dim wReserved2 As UShort
    84. <FieldOffset(6)> Dim wReserved3 As UShort
    85. <FieldOffset(8)> Dim pstrm As IntPtr 'IStream
    86. <FieldOffset(8)> Dim cArray As CArray 'BLOB
    87. End Structure
    88. <DllImport("Shell32.dll", EntryPoint:="SHGetPropertyStoreFromParsingName")>
    89. <PreserveSig> Private Shared Function SHGetPropertyStoreFromParsingName(<[In], MarshalAs(UnmanagedType.LPWStr)> pszPath As String,
    90. <[In]> pbc As IntPtr,
    91. <[In]> flags As GETPROPERTYSTOREFLAGS,
    92. <[In]> ByRef riid As Guid,
    93. <Out, MarshalAs(UnmanagedType.Interface)> ByRef ppv As IPropertyStore) As Integer
    94. End Function
    95. <DllImport("Propsys.dll", EntryPoint:="PSGetPropertyKeyFromName")>
    96. <PreserveSig> Private Shared Function PSGetPropertyKeyFromName(<[In], MarshalAs(UnmanagedType.LPWStr)> pszName As String,
    97. <Out> ByRef propkey As PROPERTYKEY) As Integer
    98. End Function
    99. <DllImport("Shlwapi.dll", EntryPoint:="SHCreateMemStream")>
    100. <PreserveSig> Private Shared Function SHCreateMemStream(<[In], MarshalAs(UnmanagedType.LPArray)> pInit As Byte(),
    101. <[In]> cbInit As Integer) As IntPtr
    102. End Function
    103. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    104. Dim PropertyStore As IPropertyStore = Nothing
    105. Dim hResult As Integer = SHGetPropertyStoreFromParsingName("D:\test.mp3", IntPtr.Zero,
    106. GETPROPERTYSTOREFLAGS.GPS_HANDLERPROPERTIESONLY Or
    107. GETPROPERTYSTOREFLAGS.GPS_READWRITE,
    108. New Guid(IID_IPropertyStore), PropertyStore)
    109. If hResult = S_OK Then
    110. Dim PROPKEY As New PROPERTYKEY
    111. If PSGetPropertyKeyFromName("System.ThumbnailStream", PROPKEY) = S_OK Then
    112. ' Bild, vorzugsweise JPG
    113. Dim PictureBytes As Byte() = IO.File.ReadAllBytes("D:\test.jpg")
    114. Dim pIStream As IntPtr = SHCreateMemStream(PictureBytes, PictureBytes.Length)
    115. If pIStream <> IntPtr.Zero Then
    116. Dim PROPVAR As New PROPVARIANT
    117. ' Bild löschen, da brauch man auch kein IStream
    118. 'PROPVAR.vt = VARTYPE.VT_EMPTY
    119. 'PROPVAR.pstrm = IntPtr.Zero
    120. PROPVAR.vt = VARTYPE.VT_STREAM
    121. PROPVAR.pstrm = pIStream
    122. If PropertyStore.SetValue(PROPKEY, PROPVAR) = S_OK Then
    123. PropertyStore.Commit()
    124. Debug.Print("OK")
    125. End If
    126. Marshal.Release(pIStream)
    127. End If
    128. End If
    129. Marshal.ReleaseComObject(PropertyStore)
    130. Else
    131. Try
    132. Marshal.ThrowExceptionForHR(hResult)
    133. Catch ex As Exception
    134. Debug.Print(ex.Message)
    135. End Try
    136. End If
    137. End Sub
    138. #Region "Interface IPropertyStore"
    139. <ComImport>
    140. <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
    141. <Guid(IID_IPropertyStore)>
    142. Private Interface IPropertyStore
    143. <PreserveSig> Function GetCount(<Out> ByRef cProps As Integer) As Integer
    144. <PreserveSig> Function GetAt(<[In]> iProp As Integer,
    145. <Out> ByRef pkey As PROPERTYKEY) As Integer
    146. <PreserveSig> Function GetValue(<[In]> ByRef key As PROPERTYKEY,
    147. <Out> ByRef pv As PROPVARIANT) As Integer
    148. <PreserveSig> Function SetValue(<[In]> ByRef key As PROPERTYKEY,
    149. <[In]> ByRef pv As PROPVARIANT) As Integer
    150. <PreserveSig> Function Commit() As Integer
    151. End Interface
    152. #End Region
    153. End Class

    Mfg -Franky-

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „-Franky-“ ()

    Ich werde heute vermutlich nicht dazu kommen mit der BASS+FFMpeg zu probieren. Aber da du eh FFMpeg in deinem BIn Ordnern hast, kannst du auch einfach FFMpeg via Commandozeile(bzw. Process.Start) benutzen.

    So kannst du das mit einer Stapelverarbeitungsdatei machen,

    Quellcode

    1. ffmpeg -i input.wav -i "Z:\PATH TO THE IMAGE\FILENAME.jpg" -map 0 -c:a libmp3lame -b:a 320K -ar 48000 -map 1 -id3v2_version 4 -metadata:s:v comment="AlbumArt" -metadata genre="Genre" -metadata artist="Artist" -metadata title="Title" output.mp3


    Ist das Problem zwar nicht gelöst, aber umgangen. Ich denke wenn du das erstmal so machst, wirst du das selbst mit Process.Start + Argument schaffen.


    Edit:
    @roepke
    Warum hast du mich nicht drauf aufmerksam gemacht, das ich falsch lag mit FFMpeg. Die Lame.exe und lame_enc.dll braucht man dafür. Was genau machst du, eine Datei konvertieren, oder ein Webradio aufzeichnen und konvertieren?
    Wie groß ist die ausgegebene Datei bei dir?

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „BitBrösel“ ()

    Ich habe eben mal die ganze Geschichte näher untersucht und mal geschaut, was die BassNet der Lame.exe denn so als Argumente, rübergibt. Es wird kein Image zugewiesen, ich probiere nach dem Abendessen mal was aus, editiere dann hier.

    Edit @roepke
    Also das mit dem EncoderLAME ganz schnell vergessen. Mit FFMpeg lag ich doch nicht ganz falsch, aber im Zusammenhang mit dem EncoderLAME. Ich war mir sicher, das ich sowas für jemanden mal mit FFMpeg gemacht hab, hier so funktioniert das mit der Lame.exe und ohne den Bassnet-EncodeLame dingens.

    C#-Quellcode

    1. int stream = Bass.BASS_StreamCreateFile(filename, 0, 0, BASSFlag.BASS_STREAM_DECODE);
    2. if(stream != 0)
    3. {
    4. int bitWidth = 32;
    5. int bitRate = 320;
    6. float sampleRate = 48.0f;
    7. string title = "TITLE";
    8. string genre = "GENRE";
    9. string artist = "ARTIST";
    10. string imagePath = "PATH TO IMAGE.jpg";
    11. string outfile = "myfile.mp3";
    12. string lameCommand = $"lame.exe --bitwidth {bitWidth} -b {bitRate} --resample {sampleRate.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture)} --ignore-tag-errors --tt \"{title}\" --ta \"{artist}\" --tg \"{genre}\" --ti \"{imagePath}\" - \"{outfile}\"";
    13. int encodeHandle = BassEnc.BASS_Encode_Start(stream, lameCommand, BASSEncode.BASS_ENCODE_FP_32BIT, null, IntPtr.Zero);
    14. if (encodeHandle == 0)
    15. {
    16. MessageBox.Show(Bass.BASS_ErrorGetCode().ToString());
    17. Close();
    18. }
    19. byte[] buffer = new byte[1048576];
    20. while (Bass.BASS_ChannelIsActive(stream) == BASSActive.BASS_ACTIVE_PLAYING)
    21. {
    22. // durch das callen dieser Funktion wird der encoder automatisch gefüttert
    23. //alternativ keinen decoding stream anlegen(sondern wie normal beim abspielen), und Bass_ChannelPlay callen
    24. //dazu würde ich dann ein Callback nutzen, sollte ENCODEPROC sein und dort diese Funktion callen
    25. int len = Bass.BASS_ChannelGetData(stream, buffer, buffer.Length);
    26. }
    27. BassEnc.BASS_Encode_Stop(encodeHandle);
    28. Bass.BASS_StreamFree(stream);
    29. }
    30. else
    31. {
    32. MessageBox.Show(Bass.BASS_ErrorGetCode().ToString());
    33. }
    Bilder
    • Unbenannt.jpg

      24,66 kB, 595×131, 79 mal angesehen

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „BitBrösel“ ()

    Hi

    Ich finde die Konstellation von Bass mit Command an lame oder ffmpeg Eigenartig. Kann man machen, wäre aber nicht mein Weg wenn ich vor diese Aufgabe stehen würde.

    Ich würde da eher mit der Windows Media Foundation oder per WinRT transcodieren und entsprechende Tags lesen/schreiben. Es werden keine zusätzliche Drittkomponenten benötigt da bereits alles in Windows vorhandenen ist.
    Mfg -Franky-
    Ich bastel im Moment an einem Mediaplayer mit der BASS. Mach mir das mit WPF richtig schön. Dabei entstand auch ein 2. Projekt, nur ein Webradio(mit der radio-browser.info API), da kommt auch noch Aufnahme rein. Da ich die Webradiostreams eh mit der BASS abspiele und auch aufnehmen einbauen werde(ohne Konvertierung), finde ich es nicht falsch, falls der User irgendwelche Encoder installiert hat, das er diese verwenden kann, daher werde ich das optional einbauen zumindest für FFMpeg, damit kenn ich mich aus und es ist recht einfach den User durch ComboBox(en)-Auswahl die Parameter einstellen zu lassen.

    So Franky, jetzt mach ich ein Thema auf, jetzt hast du mich soweit gebracht.
    @roepke

    IPropertyStore ist das COM Interface das auch vom Explorer verwendet wird um Dateieigenschaften auszulesen oder zu schreiben. Also alle Eigenschaften die Du im Explorer in der Detailansicht oder im Eigenschaften-Dialog zur Datei sehen kannst.

    Du kannst ja mal für SHGetPropertyStoreFromParsingName nur das Flag GPS_DEFAULT setzen und dann mit IPropertyStore.GetCount die Anzahl der Eigenschaften ermitteln, die man über IPropertyStore auslesen könnte. Per IPropertyStore.GetAt holst Dir den PROPERTYKEY der Eigenschaft. Mit der API PSGetNameFromPropertyKey kann man den PROPERTYKEY in einen CanonicalName konvertieren. Mit IPropertyStore.GetValue holst Dir halt den Wert der Eigenschaft -> PROPVARIANT. In PROPVARIANT.vt steht halt um was es sich handelt. Die Structure PROPVARIANT ist im obrigen Beispiel aber nicht komplett da es ja neben pstrm auch andere Member in Abhängigkeit zu PROPVARIANT.vt gibt. Na jedenfalls könntest Du, wenn Du die Eigenschaft System.ThumbnailStream für eine MP3 ausliest, den Pointer pstrm (IStream) wieder zu einem Bild konvertieren. Also der umgekehrte Weg wie oben gezeigt.
    Mfg -Franky-