Byte Array als JPG-Datei weiterverarbeiten ohne das Bild zwischenzuspeichern

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

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

    Byte Array als JPG-Datei weiterverarbeiten ohne das Bild zwischenzuspeichern

    Ich lese mit TagLib aus einer Audiodatei ein Cover Art aus. Dieses möchte ich dann beim Umwandeln mit ffmpeg wieder in die umgewandelte Audiodatei
    als metadata integrieren. ffmpeg erwartet als Parameter einen Dateinamen.
    Bisher habe ich erst diese Datei erzeugt und dann in ffmpeg verarbeitet.

    Quellcode

    1. Private Sub GetPictureData(ByVal InputAudioFile As String)
    2. Dim f As TagLib.File = TagLib.File.Create(InputAudioFile)
    3. If f.Tag.Pictures.Length >= 1 Then
    4. Dim bin As Byte() = DirectCast(f.Tag.Pictures(0).Data.Data, Byte())
    5. Dim Image As Image = ConvertBytes(bin)
    6. Image.Save("TmpCover.jpg", System.Drawing.Imaging.ImageFormat.Jpeg)
    7. End If
    8. f.Dispose()
    9. End Sub
    10. Private Function ConvertBytes(ByVal mybytes() As Byte) As Image
    11. Dim myimage As Image
    12. Dim ms As System.IO.MemoryStream = New System.IO.MemoryStream(mybytes)
    13. myimage = System.Drawing.Image.FromStream(ms)
    14. Return myimage
    15. End Function


    Geht das auch irgenwie ohne das ich die Datei immer auf der Festplatte speichern muß ?
    Zuerst: Wenn Du mit TagLib das Cover aus der einen Datei auslesen kannst, kann TagLib dann nicht auch das Cover in eine andere Datei speichern bzw. kann ffmpeg beim konvertieren nicht auch gleich die Metadaten übertragen?

    Falls nicht: Es gibt hier mehre Wege das Cover von einer Datei zu einer anderen zu übertragen. Ich würde dazu das COM-Interface IPropertyStore verwenden. Damit lässt sich das Cover auslesen (-> ByteArray) und auch wieder schreiben. Ohne zwischenspeichern auf die Festplatte.

    Alternativ kann man auch gleich die MediaFoundation oder WinRT zum konvertieren, lesen und schreiben von Metadaten verwenden.
    Mfg -Franky-

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

    Jap. ffmpeg kann alle metadaten übertragen. Nur bei den Cover Arts gibt es durch die unterschiedlichen Lossless Quellen (alac,m4a,wav,...), Probleme mit der Picture Größe im flac Format, so dass ich diese exportiere, formatiere und wieder reinschreibe.
    Leider mit dem Umweg, das erzeugte Bild erstmal nur auf der Festplatte zu speichern und dann in ffmpeg als Metadata Input wieder zu integrieren.
    Mit der COM Welt habe ich mich noch garnicht beschäftigt. Auch ffmpeg hätte wohl die Möglichkeit über pipes zu importieren.
    Beides ist aber absolutes Neuland für mich.
    Ich habe nun gehofft, das man das Bild auch ohne Zwischenspeicherung auf der HDD als Parameter an FFMPEG übergeben kann. Im Endeffekt öffnet es ja auch nur die angegebene Bilddatei als STream, Object oder weiß ich nich welche Bits und Bytes und schreibt
    es in die flac Datei.
    @doLob Und wie schaut es mit TagLib aus? Cover auslesen geht ja anscheinend und schreiben? Ich kenne mich mit TagLib und ffmpeg nicht aus und ich habe diese Woche viel um die Ohren so das ich kein Bsp. für IPropertyStore basteln kann. Denke das die anderen Mitstreiter hier Dir da unter die Arme greifen können. Daher nur ein grober Plan wie es mit IPropertyStore funktioniert.

    API SHGetPropertyStoreFromParsingName -> IPropertyStore.

    C-Quellcode

    1. EXTERN_C const IID IID_IPropertyStore;
    2. #if defined(__cplusplus) && !defined(CINTERFACE)
    3. MIDL_INTERFACE("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99")
    4. IPropertyStore : public IUnknown
    5. {
    6. public:
    7. virtual HRESULT STDMETHODCALLTYPE GetCount(
    8. /* [out] */ __RPC__out DWORD *cProps) = 0;
    9. virtual HRESULT STDMETHODCALLTYPE GetAt(
    10. /* [in] */ DWORD iProp,
    11. /* [out] */ __RPC__out PROPERTYKEY *pkey) = 0;
    12. virtual HRESULT STDMETHODCALLTYPE GetValue(
    13. /* [in] */ __RPC__in REFPROPERTYKEY key,
    14. /* [out] */ __RPC__out PROPVARIANT *pv) = 0;
    15. virtual HRESULT STDMETHODCALLTYPE SetValue(
    16. /* [in] */ __RPC__in REFPROPERTYKEY key,
    17. /* [in] */ __RPC__in REFPROPVARIANT propvar) = 0;
    18. virtual HRESULT STDMETHODCALLTYPE Commit( void) = 0;
    19. };

    IPropertyStore.GetValue <- PROPERTYKEY (System.ThumbnailStream bzw. PKEY_ThumbnailStream ({f29f85e0-4ff9-1068-ab91-08002b27b3d9},27)) -> PROPVARIANT (müsste dann ein VT_STREAM sein, Pointer auf ein IStream)
    IStream in ein ByteArray kopieren und ein Bild erstellen (ByteArray -> MemoryStream -> Bitmap). Alternativ über Reflection über GDI+-API (GdipCreateBitmapFromStream) direkt vom pIStream ein pBitmap -> .NET-Bitmap. Spart dann den Weg über einen MemoryStream und das kopiere in ein ByteArray.

    Umgekerter Weg:
    Bild in ein IStream kopieren (IStream per SHCreateMemStream erstellen), IPropertyStore.SetValue <- PROPERTYKEY (System.ThumbnailStream/ PKEY_ThumbnailStream), <- PROPVARIANT (VT_STREAM, Pointer auf das IStream mit dem Bild)
    IPropertyStore.Commit

    Bild löschen:
    IPropertyStore.SetValue <- PROPERTYKEY (System.ThumbnailStream/ PKEY_ThumbnailStream), <- PROPVARIANT (VT_Empty, IntPtr.Zero)
    IPropertyStore.Commit

    Edit: Bissel Zeit gefunden, aber... nur auf die schnelle zusammenkopiert (alles in eine Form). Nur Auslesen des Cover. Schreiben ist halt die umgekehrte Richtung.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Runtime.InteropServices
    2. Public Class Form1
    3. #Region "API"
    4. <DllImport("Shell32.dll", EntryPoint:="SHGetPropertyStoreFromParsingName")>
    5. <PreserveSig> Private Shared Function SHGetPropertyStoreFromParsingName(<[In], MarshalAs(UnmanagedType.LPWStr)> pszPath As String,
    6. <[In]> pbc As IntPtr,
    7. <[In]> flags As GETPROPERTYSTOREFLAGS,
    8. <[In]> ByRef riid As Guid,
    9. <Out, MarshalAs(UnmanagedType.Interface)> ByRef ppv As IPropertyStore) As Integer
    10. End Function
    11. <DllImport("Ole32.dll", EntryPoint:="PropVariantClear")>
    12. <PreserveSig> Private Shared Function PropVariantClear(<[In], Out> ByRef pvar As PROPVARIANT) As Integer
    13. End Function
    14. #End Region
    15. #Region "Enum"
    16. Private Enum GETPROPERTYSTOREFLAGS As Integer
    17. GPS_DEFAULT = &H0
    18. GPS_HANDLERPROPERTIESONLY = &H1
    19. GPS_READWRITE = &H2
    20. GPS_TEMPORARY = &H4
    21. GPS_FASTPROPERTIESONLY = &H8
    22. GPS_OPENSLOWITEM = &H10
    23. GPS_DELAYCREATION = &H20
    24. GPS_BESTEFFORT = &H40
    25. GPS_NO_OPLOCK = &H80
    26. GPS_PREFERQUERYPROPERTIES = &H100
    27. GPS_EXTRINSICPROPERTIES = &H200
    28. GPS_EXTRINSICPROPERTIESONLY = &H400
    29. GPS_VOLATILEPROPERTIES = &H800
    30. GPS_VOLATILEPROPERTIESONLY = &H1000
    31. GPS_MASK_VALID = &H1FFF
    32. End Enum
    33. Private Enum STREAM_SEEK As Integer
    34. STREAM_SEEK_SET = 0
    35. STREAM_SEEK_CUR = 1
    36. STREAM_SEEK_END = 2
    37. End Enum
    38. Private Enum STGC As Integer
    39. STGC_DEFAULT = 0
    40. STGC_OVERWRITE = 1
    41. STGC_ONLYIFCURRENT = 2
    42. STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE = 4
    43. STGC_CONSOLIDATE = 8
    44. End Enum
    45. Private Enum LOCKTYPE
    46. LOCK_WRITE = 1
    47. LOCK_EXCLUSIVE = 2
    48. LOCK_ONLYONCE = 4
    49. End Enum
    50. Private Enum STATFLAG As Integer
    51. STATFLAG_DEFAULT = 0
    52. STATFLAG_NONAME = 1
    53. STATFLAG_NOOPEN = 2
    54. End Enum
    55. Private Enum VARTYPE As UShort 'gekürzt
    56. VT_EMPTY = 0
    57. VT_STREAM = 66
    58. End Enum
    59. #End Region
    60. #Region "Const"
    61. Private Const S_OK As Integer = 0
    62. Private Const IID_IStream As String = "0000000c-0000-0000-c000-000000000046"
    63. Private Const IID_IPropertyStore As String = "886d8eeb-8cf2-4446-8d02-cdba1dbdcf99"
    64. Private Const PKEY_ThumbnailStream_fmtid As String = "f29f85e0-4ff9-1068-ab91-08002b27b3d9"
    65. Private Const PKEY_ThumbnailStream_pid As Integer = 27
    66. #End Region
    67. #Region "Structure"
    68. Private Structure FILETIME
    69. Dim dwLowDateTime As Integer
    70. Dim dwHighDateTime As Integer
    71. End Structure
    72. Private Structure STATSTG
    73. Dim pwcsName As IntPtr
    74. Dim type As Integer
    75. Dim cbSize As Long
    76. Dim mtime As FILETIME
    77. Dim ctime As FILETIME
    78. Dim atime As FILETIME
    79. Dim grfMode As Integer
    80. Dim grfLocksSupported As Integer
    81. Dim clsid As Guid
    82. Dim grfStateBits As Integer
    83. Dim reserved As Integer
    84. End Structure
    85. Private Structure CArray
    86. Dim cElems As UInteger
    87. Dim pElems As IntPtr
    88. End Structure
    89. <StructLayout(LayoutKind.Explicit, Size:=16)> 'gekürzt
    90. Private Structure PROPVARIANT
    91. <FieldOffset(0)> Dim vt As VARTYPE
    92. <FieldOffset(2)> Dim wReserved1 As UShort
    93. <FieldOffset(4)> Dim wReserved2 As UShort
    94. <FieldOffset(6)> Dim wReserved3 As UShort
    95. <FieldOffset(8)> Dim stream As IntPtr 'VT_STREAM
    96. <FieldOffset(8)> Dim cArray As CArray 'VT_BLOB
    97. End Structure
    98. Private Structure PROPERTYKEY
    99. Dim fmtid As Guid
    100. Dim pid As Integer
    101. Sub New(fmtid As Guid, pid As Integer)
    102. Me.fmtid = fmtid
    103. Me.pid = pid
    104. End Sub
    105. End Structure
    106. #End Region
    107. #Region "Interface IStream"
    108. <ComImport>
    109. <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
    110. <Guid(IID_IStream)>
    111. Private Interface IStream
    112. <PreserveSig> Function Read(<Out, MarshalAs(UnmanagedType.LPArray)> pv As Byte(),
    113. <[In]> cb As Long,
    114. <Out> ByRef pcbRead As Long) As Integer
    115. <PreserveSig> Function Write(<[In], MarshalAs(UnmanagedType.LPArray)> pv As Byte(),
    116. <[In]> cb As Long,
    117. <Out> ByRef pcbWritten As Long) As Integer
    118. <PreserveSig> Function Seek(<[In]> dlibMove As Long,
    119. <[In]> dwOrigin As STREAM_SEEK,
    120. <Out> ByRef plibNewPosition As Long) As Integer
    121. <PreserveSig> Function SetSize(<[In]> libNewSize As Long) As Integer
    122. <PreserveSig> Function CopyTo(<[In], MarshalAs(UnmanagedType.Interface)> pstm As IStream,
    123. <[In]> cb As Long,
    124. <Out> ByRef pcbRead As Long,
    125. <Out> ByRef pcbWritten As Long) As Integer
    126. <PreserveSig> Function Commit(<[In]> grfCommitFlags As STGC) As Integer
    127. <PreserveSig> Function Revert() As Integer
    128. <PreserveSig> Function LockRegion(<[In]> libOffset As Long,
    129. <[In]> cb As Long,
    130. <[In]> dwLockType As LOCKTYPE) As Integer
    131. <PreserveSig> Function UnlockRegion(<[In]> libOffset As Long,
    132. <[In]> cb As Long,
    133. <[In]> dwLockType As LOCKTYPE) As Integer
    134. <PreserveSig> Function Stat(<Out> ByRef pstatstg As STATSTG,
    135. <[In]> grfStatFlag As STATFLAG) As Integer
    136. <PreserveSig> Function Clone(<Out, MarshalAs(UnmanagedType.Interface)> ByRef ppstm As IStream) As Integer
    137. End Interface
    138. #End Region
    139. #Region "Interface IPropertyStore"
    140. <ComImport>
    141. <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
    142. <Guid(IID_IPropertyStore)>
    143. Private Interface IPropertyStore
    144. <PreserveSig> Function GetCount(<Out> ByRef cProps As Integer) As Integer
    145. <PreserveSig> Function GetAt(<[In]> iProp As Integer,
    146. <Out> ByRef pkey As PROPERTYKEY) As Integer
    147. <PreserveSig> Function GetValue(<[In]> ByRef key As PROPERTYKEY,
    148. <Out> ByRef pv As PROPVARIANT) As Integer
    149. <PreserveSig> Function SetValue(<[In]> ByRef key As PROPERTYKEY,
    150. <[In]> ByRef pv As PROPVARIANT) As Integer
    151. <PreserveSig> Function Commit() As Integer
    152. End Interface
    153. #End Region
    154. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    155. Dim PropertyStore As IPropertyStore = Nothing
    156. ' GPS_READWRITE = nur Metadaten die gelesen und geschrieben werden können
    157. If SHGetPropertyStoreFromParsingName("D:\Downloads\VBC_Prog\VBC_MediaFoundation\test.mp3",
    158. Nothing, GETPROPERTYSTOREFLAGS.GPS_READWRITE,
    159. New Guid(IID_IPropertyStore), PropertyStore) = S_OK Then
    160. Dim PropVar As New PROPVARIANT
    161. ' ThumbnailStream lesen -> PROPVARIANT
    162. If PropertyStore.GetValue(New PROPERTYKEY(New Guid(PKEY_ThumbnailStream_fmtid), PKEY_ThumbnailStream_pid), PropVar) = S_OK Then
    163. ' ist es ein VT_STREAM?
    164. If PropVar.vt = VARTYPE.VT_STREAM Then
    165. ' ist ein Pointer auf ein IStream vorhanden?
    166. If PropVar.stream <> IntPtr.Zero Then
    167. ' Pointer -> IStream
    168. Dim Stream As IStream = CType(Marshal.GetObjectForIUnknown(PropVar.stream), IStream)
    169. Dim StreamSize As Long
    170. ' Größe des Streams ermitteln
    171. If Stream.Seek(0, STREAM_SEEK.STREAM_SEEK_END, StreamSize) = S_OK Then
    172. ' Zurück zum Anfang des Streams
    173. If Stream.Seek(0, STREAM_SEEK.STREAM_SEEK_SET, Nothing) = S_OK Then
    174. If StreamSize > 0 Then
    175. ' ByteArray dimensionieren
    176. Dim Data As Byte() = New Byte(CInt(StreamSize) - 1) {}
    177. ' Stream in das ByteArray kopieren
    178. If Stream.Read(Data, StreamSize, Nothing) = S_OK Then
    179. ' vom ByteArray ein MemoryStream erstellen
    180. Using MemStream As New IO.MemoryStream(Data)
    181. ' Bitmap vom MemoryStream erstellen
    182. Me.BackgroundImage = New Bitmap(New Bitmap(MemStream))
    183. End Using
    184. End If
    185. End If
    186. End If
    187. End If
    188. ' IStream freigeben
    189. Marshal.ReleaseComObject(Stream)
    190. End If
    191. End If
    192. ' PROPVARIANT clear
    193. PropVariantClear(PropVar)
    194. End If
    195. ' IPropertyStore freigeben
    196. Marshal.ReleaseComObject(PropertyStore)
    197. End If
    198. End Sub
    199. End Class



    Natürlich lassen sich auch andere Metadaten über IPropertyStore auslesen und beschreibbare Metadaten auch schreiben.

    Edit2: Falls das Bild für die umkopiererei nicht angezeigt werden muss, dann kannst auch gleich direkt mit GDI+-APIs arbeiten. Also direkt vom IStream ein pBitmap erstellen (kein umkopieren in ein ByteArray -> MemoryStream -> usw. weil unnötig), evtl. das Bild bearbeiten und dann das Bild direkt wieder in ein IStream speichern (GdipSaveImageToStream) das dann per IPropertyStore gespeichert werden kann.
    Mfg -Franky-

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

    Wow, Vielen Dank für diese Einführung. Das ist ja wie eine Fremdsprache für mich.
    Um mich da einzuarbeiten brauche ich wohl etwas mehr Zeit. Ich werde das mal zum Anlass nehmen.
    Ich habe aber inzwischen einen Weg gefunden, ffmpeg anzuweisen das Cover während des Übertragens zu skalieren.
    Nun brauche ich die Metadaten garnicht auslesen und weiter bearbeiten also auch die TagLib nicht mehr.

    Danke nochmal!
    Na ja. IPropertyStore verwendet Windows selber (Explorer) um bei einem Rechtsklick auf eine Datei die Eigenschaften und Metadaten auszulesen und die meisten lassen sich auch beschreiben. Die WinRT nutzt ebenfalls IPropertyStore um Metadaten zu lesen/zu schreiben. Im übrigen kann man auch ab Win10, evtl sogar schon ab Win8, die WinRT nutzen um unterstützte Audio- und Videoformate in ein anderes Format zu transcodieren. WinRT nutzt dazu die Media Foundation. Ab WinVista könnte man auch die Media Foundation direkt zum transcodieren nutzen. Ist halt alles COM basiert. Windows bietet sehr viele Schnittstellen die in NET nicht immer vorhanden oder es zB. nur in UWP/WPF gibt, aber nicht in VB. Oder man lädt sich halt irgendwelche Nuget-Pakete herunter bzw nutzt Drittanbieter-DLLs die im Endeffekt evtl auch nur das nutzen, was in Windows bereits enthalten ist. Da kann man auch gleich die COM/API Schnittstellen selbst nutzen (ohne Verweise, Nuget oder Drittanbieter). <- Das ist halt meine Meinung dazu.
    Mfg -Franky-

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