Author Topic: Get and Set MP3 Tags with PowerShell and .NET DLLs  (Read 2859 times)

0 Members and 1 Guest are viewing this topic.

Offline SpriggsySpriggs

  • Forum Resident
  • Posts: 1145
  • Larger than life
    • View Profile
    • GitHub
Get and Set MP3 Tags with PowerShell and .NET DLLs
« on: June 12, 2021, 04:15:19 pm »
I searched the internet and found code for getting and setting MP3 tags using PowerShell so I decided to adapt it for QB64 using my pipecom library. It uses a .NET DLL for PowerShell for getting tags and the PowerShell module for setting. You will need to download and install the PowerShell module here and install it by downloading the zip and extracting it to the modules folder. One could automate an install of this module at runtime if desired. More tags can be set than the ones I have provided but if someone wanted to they could expand this out.

Code: QB64: [Select]
  1. $If TAGS = UNDEFINED Then
  2.     $Let TAGS = TRUE
  3.     Function GetArtist$ (file As String)
  4.         Dim As String cmd, stdout, stderr
  5.         Dim As Long exit_code
  6.         cmd = "PowerShell $files = Get-Item '" + Chr$(34) + file + Chr$(34) + "'; $null = [System.Reflection.Assembly]::LoadFile('" + Chr$(34) + _CWD$ + "\taglib-sharp.dll" + Chr$(34) + "');foreach($iFile in $files){$mediaFile=[TagLib.File]::Create($iFile.fullname);$mediaFile.Tag.AlbumArtists;}"
  7.         exit_code = pipecom(cmd, stdout, stderr)
  8.         Print exit_code
  9.         If stdout <> "" Then
  10.             GetArtist = Mid$(stdout, 1, Len(stdout) - 1)
  11.         End If
  12.  
  13.     Function GetAlbum$ (file As String)
  14.         Dim As String cmd, stdout, stderr
  15.         Dim As Long exit_code
  16.         cmd = "PowerShell $files = Get-Item '" + Chr$(34) + file + Chr$(34) + "'; $null = [System.Reflection.Assembly]::LoadFile('" + Chr$(34) + _CWD$ + "\taglib-sharp.dll" + Chr$(34) + "');foreach($iFile in $files){$mediaFile=[TagLib.File]::Create($iFile.fullname);$mediaFile.Tag.Album;}"
  17.         exit_code = pipecom(cmd, stdout, stderr)
  18.         If stdout <> "" Then
  19.             GetAlbum = Mid$(stdout, 1, Len(stdout) - 1)
  20.         End If
  21.  
  22.     Function GetTrack$ (file As String)
  23.         Dim As String cmd, stdout, stderr
  24.         Dim As Long exit_code
  25.         cmd = "PowerShell $files = Get-Item '" + Chr$(34) + file + Chr$(34) + "'; $null = [System.Reflection.Assembly]::LoadFile('" + Chr$(34) + _CWD$ + "\taglib-sharp.dll" + Chr$(34) + "');foreach($iFile in $files){$mediaFile=[TagLib.File]::Create($iFile.fullname);$mediaFile.Tag.Track;}"
  26.         exit_code = pipecom(cmd, stdout, stderr)
  27.         If stdout <> "" Then
  28.             GetTrack = Mid$(stdout, 1, Len(stdout) - 1)
  29.         End If
  30.  
  31.     Function GetDisc$ (file As String)
  32.         Dim As String cmd, stdout, stderr
  33.         Dim As Long exit_code
  34.         cmd = "PowerShell $files = Get-Item '" + Chr$(34) + file + Chr$(34) + "'; $null = [System.Reflection.Assembly]::LoadFile('" + Chr$(34) + _CWD$ + "\taglib-sharp.dll" + Chr$(34) + "');foreach($iFile in $files){$mediaFile=[TagLib.File]::Create($iFile.fullname);$mediaFile.Tag.Disc;}"
  35.         exit_code = pipecom(cmd, stdout, stderr)
  36.         If stdout <> "" Then
  37.             GetDisc = Mid$(stdout, 1, Len(stdout) - 1)
  38.         End If
  39.  
  40.     Function GetYear$ (file As String)
  41.         Dim As String cmd, stdout, stderr
  42.         Dim As Long exit_code
  43.         cmd = "PowerShell $files = Get-Item '" + Chr$(34) + file + Chr$(34) + "'; $null = [System.Reflection.Assembly]::LoadFile('" + Chr$(34) + _CWD$ + "\taglib-sharp.dll" + Chr$(34) + "');foreach($iFile in $files){$mediaFile=[TagLib.File]::Create($iFile.fullname);$mediaFile.Tag.Year;}"
  44.         exit_code = pipecom(cmd, stdout, stderr)
  45.         If stdout <> "" Then
  46.             GetYear = Mid$(stdout, 1, Len(stdout) - 1)
  47.         End If
  48.  
  49.     Function GetGenre$ (file As String)
  50.         Dim As String cmd, stdout, stderr
  51.         Dim As Long exit_code
  52.         cmd = "PowerShell $files = Get-Item '" + Chr$(34) + file + Chr$(34) + "'; $null = [System.Reflection.Assembly]::LoadFile('" + Chr$(34) + _CWD$ + "\taglib-sharp.dll" + Chr$(34) + "');foreach($iFile in $files){$mediaFile=[TagLib.File]::Create($iFile.fullname);$mediaFile.Tag.FirstGenre;}"
  53.         exit_code = pipecom(cmd, stdout, stderr)
  54.         If stdout <> "" Then
  55.             GetGenre = Mid$(stdout, 1, Len(stdout) - 1)
  56.         End If
  57.  
  58.     Function GetComposer$ (file As String)
  59.         Dim As String cmd, stdout, stderr
  60.         Dim As Long exit_code
  61.         cmd = "PowerShell $files = Get-Item '" + Chr$(34) + file + Chr$(34) + "'; $null = [System.Reflection.Assembly]::LoadFile('" + Chr$(34) + _CWD$ + "\taglib-sharp.dll" + Chr$(34) + "');foreach($iFile in $files){$mediaFile=[TagLib.File]::Create($iFile.fullname);$mediaFile.Tag.FirstComposer;}"
  62.         exit_code = pipecom(cmd, stdout, stderr)
  63.         If stdout <> "" Then
  64.             GetComposer = Mid$(stdout, 1, Len(stdout) - 1)
  65.         End If
  66.  
  67.     Function SetArtist& (file As String, Artist As String)
  68.         Dim As Long exit_code
  69.         exit_code = _ShellHide("PowerShell Import-Module taglib; Get-ChildItem '" + Chr$(34) + file + Chr$(34) + "' ^| set-artist '" + Chr$(34) + Artist + Chr$(34) + "'")
  70.         SetArtist = exit_code
  71.  
  72.     Function SetTitle& (file As String, Title As String)
  73.         Dim As Long exit_code
  74.         exit_code = _ShellHide("PowerShell Import-Module taglib; Get-ChildItem '" + Chr$(34) + file + Chr$(34) + "' ^| set-title '" + Chr$(34) + Title + Chr$(34) + "'")
  75.         SetTitle = exit_code
  76.  
  77.     Function SetTrack& (file As String, Track As Long)
  78.         Dim As Long exit_code
  79.         exit_code = _ShellHide("PowerShell Import-Module taglib; Get-ChildItem '" + Chr$(34) + file + Chr$(34) + "' ^| set-track " + LTrim$(Str$(Track)))
  80.         SetTrack = exit_code
  81.  
  82.     Function SetDisc& (file As String, Disc As Long)
  83.         Dim As Long exit_code
  84.         exit_code = _ShellHide("PowerShell Import-Module taglib; Get-ChildItem '" + Chr$(34) + file + Chr$(34) + "' ^| set-disc " + LTrim$(Str$(Disc)))
  85.         SetDisc = exit_code
  86.  
  87.     '$INCLUDE:'pipecomqb64.bas'

pipecomqb64.bas:
Code: QB64: [Select]
  1. $If PIPECOM = UNDEFINED Then
  2.     $Let PIPECOM = TRUE
  3.     Function pipecom& (cmd As String, stdout As String, stderr As String)
  4.         $If WIN Then
  5.             Type SECURITY_ATTRIBUTES
  6.                 As Long nLength
  7.                 $If 64BIT Then
  8.                     As Long padding
  9.                 $End If
  10.                 As _Offset lpSecurityDescriptor
  11.                 As Long bInheritHandle
  12.                 $If 64BIT Then
  13.                     As Long padding2
  14.                 $End If
  15.             End Type
  16.  
  17.             Type STARTUPINFO
  18.                 As Long cb
  19.                 $If 64BIT Then
  20.                     As Long padding
  21.                 $End If
  22.                 As _Offset lpReserved, lpDesktop, lpTitle
  23.                 As Long dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags
  24.                 As Integer wShowWindow, cbReserved2
  25.                 $If 64BIT Then
  26.                     As Long padding2
  27.                 $End If
  28.                 As _Offset lpReserved2, hStdInput, hStdOutput, hStdError
  29.             End Type
  30.  
  31.             Type PROCESS_INFORMATION
  32.                 As _Offset hProcess, hThread
  33.                 As Long dwProcessId
  34.                 $If 64BIT Then
  35.                     As Long padding
  36.                 $End If
  37.             End Type
  38.  
  39.             Const STARTF_USESTDHANDLES = &H00000100
  40.             Const CREATE_NO_WINDOW = &H8000000
  41.  
  42.             Const INFINITE = 4294967295
  43.             Const WAIT_FAILED = &HFFFFFFFF
  44.  
  45.             Declare CustomType Library
  46.                 Function CreatePipe%% (ByVal hReadPipe As _Offset, Byval hWritePipe As _Offset, Byval lpPipeAttributes As _Offset, Byval nSize As Long)
  47.                 Function CreateProcess%% Alias CreateProcessA (ByVal lpApplicationName As _Offset, Byval lpCommandLine As _Offset, Byval lpProcessAttributes As _Offset, Byval lpThreadAttributes As _Offset, Byval bInheritHandles As Integer, Byval dwCreationFlags As Long, Byval lpEnvironment As _Offset, Byval lpCurrentDirectory As _Offset, Byval lpStartupInfor As _Offset, Byval lpProcessInformation As _Offset)
  48.                 Function GetExitCodeProcess%% (ByVal hProcess As _Offset, Byval lpExitCode As _Offset)
  49.                 Function HandleClose%% Alias CloseHandle (ByVal hObject As _Offset)
  50.                 Function ReadFile%% (ByVal hFile As _Offset, Byval lpBuffer As _Offset, Byval nNumberOfBytesToRead As Long, Byval lpNumberOfBytesRead As _Offset, Byval lpOverlapped As _Offset)
  51.                 Function WaitForSingleObject& (ByVal hHandle As _Offset, Byval dwMilliseconds As Long)
  52.             End Declare
  53.  
  54.             Dim As _Byte ok: ok = 1
  55.             Dim As _Offset hStdOutPipeRead, hStdOutPipeWrite, hStdReadPipeError, hStdOutPipeError
  56.             Dim As SECURITY_ATTRIBUTES sa: sa.nLength = Len(sa): sa.lpSecurityDescriptor = 0: sa.bInheritHandle = 1
  57.  
  58.             If CreatePipe(_Offset(hStdOutPipeRead), _Offset(hStdOutPipeWrite), _Offset(sa), 0) = 0 Then
  59.                 pipecom = -1
  60.                 Exit Function
  61.             End If
  62.  
  63.             If CreatePipe(_Offset(hStdReadPipeError), _Offset(hStdOutPipeError), _Offset(sa), 0) = 0 Then
  64.                 pipecom = -1
  65.                 Exit Function
  66.             End If
  67.  
  68.             Dim As STARTUPINFO si
  69.             si.cb = Len(si)
  70.             si.dwFlags = STARTF_USESTDHANDLES
  71.             si.hStdError = hStdOutPipeError
  72.             si.hStdOutput = hStdOutPipeWrite
  73.             si.hStdInput = 0
  74.             Dim As PROCESS_INFORMATION pi
  75.             Dim As _Offset lpApplicationName
  76.             Dim As String fullcmd: fullcmd = "cmd /c " + cmd + Chr$(0)
  77.             Dim As String lpCommandLine: lpCommandLine = fullcmd
  78.             Dim As _Offset lpProcessAttributes, lpThreadAttributes
  79.             Dim As Integer bInheritHandles: bInheritHandles = 1
  80.             Dim As Long dwCreationFlags: dwCreationFlags = CREATE_NO_WINDOW
  81.             Dim As _Offset lpEnvironment, lpCurrentDirectory
  82.         ok = CreateProcess(lpApplicationName,_
  83.         _Offset(lpCommandLine),_
  84.         lpProcessAttributes,_
  85.         lpThreadAttributes,_
  86.         bInheritHandles,_
  87.         dwCreationFlags,_
  88.         lpEnvironment,_
  89.         lpCurrentDirectory,_
  90.         _Offset(si),_
  91.         _Offset(pi))
  92.             If ok = 0 Then
  93.                 pipecom = -1
  94.                 Exit Function
  95.             End If
  96.  
  97.             ok = HandleClose(hStdOutPipeWrite)
  98.             ok = HandleClose(hStdOutPipeError)
  99.  
  100.             Dim As String buf: buf = Space$(4096 + 1)
  101.             Dim As Long dwRead
  102.             While ReadFile(hStdOutPipeRead, _Offset(buf), 4096, _Offset(dwRead), 0) <> 0 And dwRead > 0
  103.                 buf = Mid$(buf, 1, dwRead)
  104.                 GoSub RemoveChr13
  105.                 stdout = stdout + buf
  106.                 buf = Space$(4096 + 1)
  107.             Wend
  108.  
  109.             While ReadFile(hStdReadPipeError, _Offset(buf), 4096, _Offset(dwRead), 0) <> 0 And dwRead > 0
  110.                 buf = Mid$(buf, 1, dwRead)
  111.                 GoSub RemoveChr13
  112.                 stderr = stderr + buf
  113.                 buf = Space$(4096 + 1)
  114.             Wend
  115.  
  116.             Dim As Long exit_code, ex_stat
  117.             If WaitForSingleObject(pi.hProcess, INFINITE) <> WAIT_FAILED Then
  118.                 If GetExitCodeProcess(pi.hProcess, _Offset(exit_code)) Then
  119.                     ex_stat = 1
  120.                 End If
  121.             End If
  122.  
  123.             ok = HandleClose(hStdOutPipeRead)
  124.             ok = HandleClose(hStdReadPipeError)
  125.             If ex_stat = 1 Then
  126.                 pipecom = exit_code
  127.             Else
  128.                 pipecom = -1
  129.             End If
  130.  
  131.             Exit Function
  132.  
  133.             RemoveChr13:
  134.             Dim As Long j
  135.             j = InStr(buf, Chr$(13))
  136.             Do While j
  137.                 buf = Left$(buf, j - 1) + Mid$(buf, j + 1)
  138.                 j = InStr(buf, Chr$(13))
  139.             Loop
  140.             Return
  141.         $Else
  142.             Declare CustomType Library
  143.             Function popen%& (cmd As String, readtype As String)
  144.             Function feof& (ByVal stream As _Offset)
  145.             Function fgets$ (str As String, Byval n As Long, Byval stream As _Offset)
  146.             Function pclose& (ByVal stream As _Offset)
  147.             End Declare
  148.  
  149.             Declare Library
  150.             Function WEXITSTATUS& (ByVal stat_val As Long)
  151.             End Declare
  152.  
  153.             Dim As String pipecom_buffer
  154.             Dim As _Offset stream
  155.  
  156.             Dim buffer As String * 4096
  157.             If _FileExists("pipestderr") Then
  158.             Kill "pipestderr"
  159.             End If
  160.             stream = popen(cmd + " 2>pipestderr", "r")
  161.             If stream Then
  162.             While feof(stream) = 0
  163.             If fgets(buffer, 4096, stream) <> "" And feof(stream) = 0 Then
  164.             stdout = stdout + Mid$(buffer, 1, InStr(buffer, Chr$(0)) - 1)
  165.             End If
  166.             Wend
  167.             Dim As Long status, exit_code
  168.             status = pclose(stream)
  169.             exit_code = WEXITSTATUS(status)
  170.             If _FileExists("pipestderr") Then
  171.             Dim As Integer errfile
  172.             errfile = FreeFile
  173.             Open "pipestderr" For Binary As #errfile
  174.             If LOF(errfile) > 0 Then
  175.             stderr = Space$(LOF(errfile))
  176.             Get #errfile, , stderr
  177.             End If
  178.             Close #errfile
  179.             Kill "pipestderr"
  180.             End If
  181.             pipecom = exit_code
  182.             Else
  183.             pipecom = -1
  184.             End If
  185.         $End If
  186.  
  187.     Function pipecom_lite$ (cmd As String)
  188.         Dim As Long a
  189.         Dim As String stdout, stderr
  190.         a = pipecom(cmd, stdout, stderr)
  191.         If stderr <> "" Then
  192.             pipecom_lite = stderr
  193.         Else
  194.             pipecom_lite = stdout
  195.         End If

If you choose to only GET tags, rather than SET them, you will only need this DLL: 
« Last Edit: June 12, 2021, 04:17:10 pm by SpriggsySpriggs »
Shuwatch!