【问题标题】:Encoding Spotify URI to Spotify Codes将 Spotify URI 编码为 Spotify 代码
【发布时间】:2020-09-19 02:37:23
【问题描述】:

Spotify Codes 是小条形码,可让您分享歌曲、艺术家、用户、播放列表等。

它们在“条”的不同高度对信息进行编码。 23 个条形可以有 8 个离散高度,这意味着 8^23 个不同的可能条形码。

Spotify 根据其 URI 架构生成条形码。这个 URI spotify:playlist:37i9dQZF1DXcBWIGoYBM5M 被映射到这个条形码:

URI 中的信息 (62^22) 比代码多得多。您如何将 URI 映射到条形码?似乎您不能简单地直接对 URI 进行编码。有关更多背景信息,请参阅我对这个问题的“答案”:https://stackoverflow.com/a/62120952/10703868

【问题讨论】:

    标签: encoding hash spotify barcode


    【解决方案1】:

    专利解释了一般过程,这是我发现的。

    This is a more recent patent

    当使用 Spotify 代码生成器时,网站会向 https://scannables.scdn.co/uri/plain/[format]/[background-color-in-hex]/[code-color-in-text]/[size]/[spotify-URI] 发出请求。

    使用 Burp Suite,当通过 Spotify 扫描代码时,应用会向 Spotify 的 API 发送请求:https://spclient.wg.spotify.com/scannable-id/id/[CODE]?format=json,其中 [CODE] 是您要查找的媒体参考。此请求可以通过 python 发出,但只能使用通过应用程序生成的 [TOKEN],因为这是获得正确范围的唯一方法。应用令牌大约半小时后过期。

    import requests
    
    head={
    "X-Client-Id": "58bd3c95768941ea9eb4350aaa033eb3",
    "Accept-Encoding": "gzip, deflate",
    "Connection": "close",
    "App-Platform": "iOS",
    "Accept": "*/*",
    "User-Agent": "Spotify/8.5.68 iOS/13.4 (iPhone9,3)",
    "Accept-Language": "en",
    "Authorization": "Bearer [TOKEN]", 
    "Spotify-App-Version": "8.5.68"}
    
    response = requests.get('https://spclient.wg.spotify.com:443/scannable-id/id/26560102031?format=json', headers=head)
    
    print(response)
    print(response.json())
    

    返回:

    <Response [200]>
    {'target': 'spotify:playlist:37i9dQZF1DXcBWIGoYBM5M'}
    

    所以 26560102031 是您播放列表的媒体参考。

    该专利指出,首先检测代码,然后可能使用格雷表将其转换为 63 位。例如 361354354471425226605 被编码为 010 101 001 010 111 110 010 111 110 110 100 001 110 011 111 011 011 101 101 000 111。

    但是发送到 API 的代码是 6875667268,我不确定媒体引用是如何生成的,但这是查找表中使用的数字。

    参考包含整数 0-9 与 0-7 的灰表相比,这意味着已使用使用普通二进制的算法。该专利谈到使用卷积码,然后使用维特比算法进行纠错,所以这可能是它的输出。如果没有我相信的状态,就不可能重建的东西。不过,如果您能更好地解释该专利,我会很感兴趣。

    此媒体引用为 10 位数字,但其他媒体引用为 11 或 12。

    这里还有两个原始距离示例,灰表二进制和媒体参考:

    1.

    022673352171662032460

    000 011 011 101 100 010 010 111 011 001 100 001 101 101 011 000 010 011 110 101 000

    67775490487

    2。 574146602473467556050

    111 100 110 001 110 101 101 000 011 110 100 010 110 101 100 111 111 101 000 111 000

    57639171874

    编辑:

    一些额外的信息: 网上有一些帖子描述了如何将任何文本(例如 spotify:playlist:HelloWorld)编码为代码,但这不再有效。

    我还通过代理发现,您可以使用域在代码上方获取曲目的专辑封面。这表明 Spotify 的 API 和这个可扫描的 URL 比以前想象的更紧密地集成在一起。因为它不仅存储 URI 及其代码,还可以验证 URI 并返回更新的专辑封面。

    https://scannables.scdn.co/uri/800/spotify%3Atrack%3A0J8oh5MAMyUPRIgflnjwmB

    【讨论】:

    • 感谢您提供非常好的信息。关于你得到的价值观的一些问题。第一个媒体引用 (26560102031) 为我返回此 spotify:track:1ykrctzPhcSS9GS3aHdtMt,而不是播放列表。其他两个媒体引用返回 spotify:user:jimmylavallin:playlist:2hXLRTDrNa4rG1XyM0ngT1spotify:user:spotify:playlist:37i9dQZF1DWZq91oLsHZvy。这就是你得到的吗?
    • 啊,看来我只是复制了错误的代码。您问题中 Spotify 代码的媒体参考是 57268659651,另外两个是正确的,只是随机播放列表。我尝试了很长时间将距离转换为媒体参考,但没有成功。
    • 很酷,谢谢!我正在研究它,但我敢打赌我们做不到。如果我发现了什么,我会告诉你的。
    • Archie,我在这里写了一篇关于这些代码的文章:boonepeter.github.io/posts/2020-11-10-spotify-codes
    【解决方案2】:

    您的怀疑是正确的 - 他们正在使用查找表。有关所有有趣的技术细节,请在此处获取相关专利:https://data.epo.org/publication-server/rest/v1.0/publication-dates/20190220/patents/EP3444755NWA1/document.pdf

    【讨论】:

    • 哇,不错的发现!
    【解决方案3】:

    非常有趣的讨论。一直被条形码所吸引,所以我不得不看一看。我单独对条形码进行了一些分析(没有访问媒体引用的 API),并认为我已经弄清楚了基本的编码过程。但是,基于上面的两个示例,我不相信我从媒体引用到 37 位向量的映射是正确的(即它适用于情况 2,但不适用于情况 1)。无论如何,如果你还有几对,最后一部分应该很容易解决。告诉我。

    想弄清楚这一点的人,请不要阅读下面的剧透!

    事实证明,专利中概述的基本过程是正确的,但缺乏细节。我将在下面使用上面的示例进行总结。我实际上是反过来分析的,这就是为什么我认为除了步骤(1)之外的代码描述基本上是正确的,即我生成了 45 个条码,并且所有匹配的条码都有这个代码。

    1. Map the media reference as integer to 37 bit vector. 
    Something like write number in base 2, with lowest significant bit 
    on the left and zero-padding on right if necessary. 
       57639171874 -> 0100010011101111111100011101011010110
    
    2. Calculate CRC-8-CCITT, i.e. generator x^8 + x^2 + x + 1
       The following steps are needed to calculate the 8 CRC bits:
    
       Pad with 3 bits on the right:
       01000100 11101111 11110001 11010110 10110000
       Reverse bytes:
       00100010 11110111 10001111 01101011 00001101
       Calculate CRC as normal (highest order degree on the left):
       -> 11001100
       Reverse CRC:
       -> 00110011
       Invert check:
       -> 11001100
       Finally append to step 1 result:
       01000100 11101111 11110001 11010110 10110110 01100
    
    3. Convolutionally encode the 45 bits using the common generator
    polynomials (1011011, 1111001) in binary with puncture pattern 
    110110 (or 101, 110 on each stream). The result of step 2 is 
    encoded using tail-biting, meaning we begin the shift register 
    in the state of the last 6 bits of the 45 long input vector. 
    
      Prepend stream with last 6 bits of data:
      001100 01000100 11101111 11110001 11010110 10110110 01100
      Encode using first generator:
      (a) 100011100111110100110011110100000010001001011
      Encode using 2nd generator:
      (b) 110011100010110110110100101101011100110011011
      Interleave bits (abab...):
      11010000111111000010111011110011010011110001...
      1010111001110001000101011000010110000111001111
      Puncture every third bit:
      111000111100101111101110111001011100110000100100011100110011
    
    4. Permute data by choosing indices 0, 7, 14, 21, 28, 35, 42, 49, 
    56, 3, 10..., i.e. incrementing 7 modulo 60. (Note: unpermute by 
    incrementing 43 mod 60).
    
      The encoded sequence after permuting is
      111100110001110101101000011110010110101100111111101000111000
    
    5. The final step is to map back to bar lengths 0 to 7 using the
    gray map (000,001,011,010,110,111,101,100). This gives the 20 bar 
    encoding. As noted before, add three bars: short one on each end 
    and a long one in the middle. 
    

    更新:我添加了一个条形码(级别)解码器(假设没有错误)和一个遵循上述描述的替代编码器,而不是等效的线性代数方法。希望这更清楚一点。

    更新 2:去掉了大多数硬编码数组以说明它们是如何生成的。

    线性代数方法定义了线性变换 (spotify_generator) 和掩码,以将 37 位输入映射到 60 位卷积编码数据。掩码是对 8 位反转 CRC 进行卷积编码的结果。 spotify_generator 是一个 37x60 矩阵,它实现了 CRC(一个 37x45 矩阵)和卷积码(一个 45x60 矩阵)的生成器的乘积。您可以通过将函数应用于适当大小的生成器矩阵的每一行来从编码函数创建生成器矩阵。例如,将 8 位添加到每个 37 位数据向量的 CRC 函数应用于 37x37 单位矩阵的每一行。

    import numpy as np
    import crccheck
    
    
    # Utils for conversion between int, array of binary
    # and array of bytes (as ints)
    def int_to_bin(num, length, endian):
        if endian == 'l':
            return [num >> i & 1 for i in range(0, length)]
        elif endian == 'b':
            return [num >> i & 1 for i in range(length-1, -1, -1)]
    
    def bin_to_int(bin,length):
        return int("".join([str(bin[i]) for i in range(length-1,-1,-1)]),2)
    
    def bin_to_bytes(bin, length):
        b = bin[0:length] + [0] * (-length % 8)
        return [(b[i]<<7) + (b[i+1]<<6) + (b[i+2]<<5) + (b[i+3]<<4) + 
            (b[i+4]<<3) + (b[i+5]<<2) + (b[i+6]<<1) + b[i+7] for i in range(0,len(b),8)]
        
    # Return the circular right shift of an array by 'n' positions    
    def shift_right(arr, n):
        return arr[-n % len(arr):len(arr):] + arr[0:-n % len(arr)]
    
    gray_code = [0,1,3,2,7,6,4,5]
    gray_code_inv = [[0,0,0],[0,0,1],[0,1,1],[0,1,0],
                     [1,1,0],[1,1,1],[1,0,1],[1,0,0]]
    
    # CRC using Rocksoft model: 
    # NOTE: this is not quite any of their predefined CRC's
    # 8: number of check bits (degree of poly)
    # 0x7: representation of poly without high term (x^8+x^2+x+1)
    # 0x0: initial fill of register
    # True: byte reverse data
    # True: byte reverse check
    # 0xff: Mask check (i.e. invert)
    spotify_crc = crccheck.crc.Crc(8, 0x7, 0x0, True, True, 0xff)
    
    def calc_spotify_crc(bin37):
        bytes = bin_to_bytes(bin37, 37)
        return int_to_bin(spotify_crc.calc(bytes), 8, 'b')
    
    def check_spotify_crc(bin45):
        data = bin_to_bytes(bin45,37)
        return spotify_crc.calc(data) == bin_to_bytes(bin45[37:], 8)[0]
    
    # Simple convolutional encoder
    def encode_cc(dat):
        gen1 = [1,0,1,1,0,1,1]
        gen2 = [1,1,1,1,0,0,1]
        punct = [1,1,0]
        dat_pad = dat[-6:] + dat # 6 bits are needed to initialize
                                 # register for tail-biting
        stream1 = np.convolve(dat_pad, gen1, mode='valid') % 2
        stream2 = np.convolve(dat_pad, gen2, mode='valid') % 2
        enc = [val for pair in zip(stream1, stream2) for val in pair]
        return [enc[i] for i in range(len(enc)) if punct[i % 3]]
        
    # To create a generator matrix for a code, we encode each row
    # of the identity matrix. Note that the CRC is not quite linear
    # because of the check mask so we apply the lamda function to
    # invert it. Given a 37 bit media reference we can encode by
    #     ref * spotify_generator + spotify_mask (mod 2)
    _i37 = np.identity(37, dtype=bool)
    crc_generator = [_i37[r].tolist() + 
              list(map(lambda x : 1-x, calc_spotify_crc(_i37[r].tolist())))
              for r in range(37)]
    spotify_generator = 1*np.array([encode_cc(crc_generator[r]) for r in range(37)], dtype=bool)  
    del _i37
    
    spotify_mask = 1*np.array(encode_cc(37*[0] + 8*[1]), dtype=bool) 
        
    # The following matrix is used to "invert" the convolutional code.
    # In particular, we choose a 45 vector basis for the columns of the
    # generator matrix (by deleting those in positions equal to 2 mod 4)
    # and then inverting the matrix. By selecting the corresponding 45 
    # elements of the convolutionally encoded vector and multiplying 
    # on the right by this matrix, we get back to the unencoded data,
    # assuming there are no errors.
    # Note: numpy does not invert binary matrices, i.e. GF(2), so we
    # hard code the following 3 row vectors to generate the matrix.
    conv_gen = [[0,1,0,1,1,1,1,0,1,1,0,0,0,1]+31*[0],
                [1,0,1,0,1,0,1,0,0,0,1,1,1] + 32*[0],
                [0,0,1,0,1,1,1,1,1,1,0,0,1] + 32*[0] ]
    
    conv_generator_inv = 1*np.array([shift_right(conv_gen[(s-27) % 3],s) for s in range(27,72)], dtype=bool) 
    
    
    # Given an integer media reference, returns list of 20 barcode levels
    def spotify_bar_code(ref):
        bin37 = np.array([int_to_bin(ref, 37, 'l')], dtype=bool)
        enc = (np.add(1*np.dot(bin37, spotify_generator), spotify_mask) % 2).flatten()
        perm = [enc[7*i % 60] for i in range(60)]
        return [gray_code[4*perm[i]+2*perm[i+1]+perm[i+2]] for i in range(0,len(perm),3)]
        
    # Equivalent function but using CRC and CC encoders.
    def spotify_bar_code2(ref):
        bin37 = int_to_bin(ref, 37, 'l')
        enc_crc = bin37 + calc_spotify_crc(bin37)
        enc_cc = encode_cc(enc_crc)
        perm = [enc_cc[7*i % 60] for i in range(60)]
        return [gray_code[4*perm[i]+2*perm[i+1]+perm[i+2]] for i in range(0,len(perm),3)]
        
    # Given 20 (clean) barcode levels, returns media reference
    def spotify_bar_decode(levels):
        level_bits = np.array([gray_code_inv[levels[i]] for i in range(20)], dtype=bool).flatten()
        conv_bits = [level_bits[43*i % 60] for i in range(60)]
        cols = [i for i in range(60) if i % 4 != 2] # columns to invert
        conv_bits45 = np.array([conv_bits[c] for c in cols], dtype=bool)
        bin45 = (1*np.dot(conv_bits45, conv_generator_inv) % 2).tolist()
        if check_spotify_crc(bin45):
            return bin_to_int(bin45, 37)
        else:
            print('Error in levels; Use real decoder!!!')
            return -1
    

    还有例子:

    >>> levels = [5,7,4,1,4,6,6,0,2,4,3,4,6,7,5,5,6,0,5,0]
    >>> spotify_bar_decode(levels)
    57639171874
    >>> spotify_barcode(57639171874)
    [5, 7, 4, 1, 4, 6, 6, 0, 2, 4, 3, 4, 6, 7, 5, 5, 6, 0, 5, 0]
    

    【讨论】:

    • 我想我应该提一下,要从条码长度倒退到媒体参考,我们确实需要应用解码器来校正条码长度。但是为了快速和肮脏,我们可以通过乘以奇偶校验矩阵来验证条形码长度是否正确(即形成正确的码字而没有错误),如果是这样,只需应用类似的线性变换来“撤消”编码.
    • 你的答案是正确的编码!我使用上面提到的媒体参考来获取 Spotify 代码并根据您的编码检查它们并且它们匹配。您是如何生成spotify_generator_compact 的?你能像你在评论中提到的那样展示你将如何倒退吗?假设不需要纠错。
    • 哦,太好了,所以它适用于您的所有示例?我有点困惑为什么它与上面的第一个示例不匹配。
    • 我将在接下来的几天内更新代码以进行“假”解码。我很高兴向您发送更详细的 pdf,说明我是如何完成线性代数步骤的。真的很喜欢你在另一页上写的文章。
    猜你喜欢
    • 1970-01-01
    • 2012-05-14
    • 2013-10-22
    • 1970-01-01
    • 1970-01-01
    • 2015-01-02
    • 2018-05-21
    • 2019-05-03
    • 2021-04-02
    相关资源
    最近更新 更多