mikebrady / shairport-sync

AirPlay and AirPlay 2 audio player
Other
7.3k stars 574 forks source link

Metadata encoding -- Cover art #806

Closed OneBobone closed 5 years ago

OneBobone commented 5 years ago

Fantastic SW, thank you so much! Am having some issues encoding the data sent over UDP. Metadata text works fine when I encode using UTF8 (ASCII is not international enough), but I am having some problems with the cover art. It "looks" similar to other cover art jpegs that I have, but there are some slight differences. UTF7 looks slightly better for a jpeg but destroys int'l letters. What is the right way to encode/decode the UDP stream? Also, I get some short data in coreastm ("song time" = duration?), but cannot decipher it.

mikebrady commented 5 years ago

Yep, it's probably UTF-8. It comes from the audio source directly, as does the "astm" data. I don't know what it means -- song time in milliseconds?

Cover art can be any picture format; again it comes straight from the audio source and is encoded in BASE64 form. I have observed JPEGs and PNGs.

OneBobone commented 5 years ago

Sorry for being dense:

Hmm, I maybe don’t need to know all the background, but my problem remains.
The data encoded in utf-8 from the udp stream minus the header “ssndpict” looks very similar, but still different than other jpgs. The characters/bytes have the same position but are sometimes slightly different. The file will not be accepted as pictures in viewers (chunked or not) or online picture repair sites. It makes me think that the utf8 bytes are encoded correctly, but the more advanced bytes are not.

Or should I separate out the header, then encode back the rest to base64 and then decode to some other format for the jpg file? (The middle step is obviously unnecessary, but written for clarity)

mikebrady commented 5 years ago

The metadata was originally intended for output over a unix pipe. Later work by another contributor put it out on UDP. Have a look in the sample metadata reader for hints, and also have a look in RTSP.c for some information.

There doesn't seem to be any metadata indicating the format of the picture -- you have to look at the raw data.

I imagine the artwork is copyright, BTW, so sharing it with other sites is really a questionable practice.

OneBobone commented 5 years ago

Thx for the tip. WIll have a look. To other public sites, definitely questionable, for private use between screens, I think less. :-)

OneBobone commented 5 years ago

UTF7 gives the closest results, but not good enough. Think I have to give up for now... I am puzzled.

OneBobone commented 5 years ago

I finally solved this more more less well. There where two problems:

Read also (wish it were better sorted): https://github.com/tchapi/shairport-sync-ui/blob/master/DMAP_DAAP_Codes.md

For Cover art, it is best to save the UDP byte stream directly to a file. Howeever, I searched for "PICT" in the UTF8 encoded string (the right coding), and then used that position to cut out the right array from the UDP stream.

Chunking does not seem to work perfectly and caused some headache. Not all bytes make it through in the second chunk. The final "ssncpcen" after the last chunk is also not present (maybe it is in the last chunk...)

    hs.Writelog(LogLabel, "Opened Shairport UDP Server")
    Try
        While hs.GetVar("ApReceive") = True
            Dim UDPbytes As Byte() = listener.Receive(groupEP)
            Metadata = Encoding.UTF8.GetString(UDPbytes, 0, UDPbytes.Length)
            PICTpos = instr(1, Metadata, "ssncPICT")
            astmpos = instr(1, Metadata, "coreastm")

            If PICTpos > 0 Then ' This is where image creation takes place
                If instr(1, Metadata, "ssncchnk") Or UDPbytes.Length > 10000 Then ' Discard a standard thumbnail 
                    Dim filePath As String = "C:\Program Files\HomeSeer HS3\html\images\Shairport\Thumbnails\"
                    Dim fileName As String = trim(hs.GetVar("ApArtist")) & " - " & trim(hs.GetVar("ApTitle")) & ".jpg"
                    PICTpos = PICTpos + 7
                    Dim mySubArray(Ubound(UDPbytes) - PICTpos) As Byte
                    System.Array.Copy(UDPbytes, PICTpos, mySubArray, 0, mySubArray.Length - 1) 'Could be: mySubArray.Length - 0 ?
                    My.Computer.FileSystem.WriteAllBytes(filePath & fileName, mySubArray, True) ' Always append
                    File.Copy(filePath & fileName, filePath & "CurrentApThumbnail.jpg", True) ' Always overwrite
                    Hs.waitsecs(0.3)
                End If
            ElseIf astmpos > 0 Then ' Determine Duration
                astmpos = astmpos + 7
                Dim mySubArray(3) As Byte
                System.Array.Copy(UDPbytes, astmpos, mySubArray, 0, 4)
                Array.Reverse(mySubArray)
                hs.SaveVar("ApDuration", int(BitConverter.ToInt32(mySubArray, 0) / 100))
            Else
                Hs.RunScriptFunc("Airplay.vb", "Main", Metadata, True, False) ' This is where the core parsing of metadata takes place
            End If
        End While
    Catch e As SocketException
        hs.Writelog(LogLabel, e)
    Finally
        listener.Close()
        hs.Writelog(LogLabel, "Closed Shairport UDP Server")
    End Try

The latter part decipers the song duration or coreastm = SongtTime. This was really tricky as the insignificant looking data following coreastm is not a string, but a 4-byte Integer. On top of that in reversed order compared to my windows environment! The resulting integer is in milliseconds (but I need a 10-multiple of seconds for my integration).

The issue is quite closed, but it would be great if the chunking autor could provide more documentation.