我正在尝试使用python解码ID3v2(MP3标头)协议。要解码的数据格式如下。
s1
,s2
,... sn-1
是unicode(utf-16 / utf-8)字符串,最后一个字符串'sn'可以是unicode或二进制字符串。< / p>
data = s1+delimiters+s2+delimiters+...+sn
其中,utf-16的分隔符为'\x00'+'\x00'
和utf-8的分隔符是'\x00'
我得到data
以及unicode类型。现在我必须从s1
中提取所有字符串(s2
,sn
,... data
)。为此,我使用split()
如下,
#!/usr/bin/python
def extractStrings(encoding_type, data):
if(encoding_type == "utf-8"): delimitors = '\x00'
else: delimitors = '\x00'+'\x00'
return data.split(delimitors)
def main():
# Set-1
encoding_type = "utf-8"
delimitors = '\x00'
s1="Hello".encode(encoding_type)
s2="world".encode(encoding_type)
data = s1+delimitors+s2
print extractStrings(encoding_type, data)
# Set-2
encoding_type = "utf-16"
delimitors = '\x00'+'\x00'
s1="Hello".encode(encoding_type)
s2="world".encode(encoding_type)
data = s1+delimitors+s2
print extractStrings(encoding_type, data)
if __name__ == "__main__":
main()
输出:
['Hello', 'world']
['\xff\xfeH\x00e\x00l\x00l\x00o', '\x00\xff\xfew\x00o\x00r\x00l\x00d\x00']
它适用于set-1数据,但不适用于set-2。 因为,'数据'在set-2
中'\xff\xfeH\x00e\x00l\x00l\x00o\x00\x00\x00\xff\xfew\x00o\x00r\x00l\x00d\x00'
^ ^
在分隔符之前有一个额外的'\x00'
,由于字母'0',它无法正常工作。
任何人都可以帮我解决这两种情况的“数据”吗?
更新
我会尝试简单地解决这个问题。 s1 = encoded(utf-8 / utf-16)string
s2 =二进制字符串(不是unicode)
utf-16的分隔符为'\x00'+'\x00'
,utf-8的分隔符为'\x00'
data =(s1 + delimiter)+ s2
任何人都可以帮助我从“数据”中提取s1和s2吗?
Update2:解决方案
以下代码适用于我的要求,
def splitNullTerminatedEncStrings(self, data, encoding_type, no_of_splits):
data_dec = data.decode(encoding_type, 'ignore')
chunks = data_dec.split('\x00', no_of_splits)
enc_str_lst = []
for data_dec_seg in chunks[:-1]:
enc_str_lst.append(data_dec_seg.encode(encoding_type))
data_dec_chunks = '\x00'.join(chunks[:-1])
if(data_dec_chunks): data_dec_chunks += '\x00'
data_chunks = data_dec_chunks.encode(encoding_type)
data_chunks_len = len(data_chunks)
enc_str_lst.append(data[data_chunks_len:]) # last segment
return enc_str_lst
答案 0 :(得分:4)
其中,utf-16的分隔符为'\ x00'+'\ x00',utf-8的分隔符为'\ x00'
不完全是。 UTF-16的分隔符仅在代码单元边界处为\0\0
。一个代码单元末尾的一个\0
在另一个代码单元的开头跟\0
后不构成分隔符。 ID3标准,谈论字节“同步”意味着情况并非如此,但这是错误的。
[旁白:不幸的是,许多标签阅读工具确实采用了这种方式,结果是任何具有双零字节的序列(例如U + 0100,U + 0061 Āa
为UTF- 16BE,或者,正如您所发现的,UTF-16LE中字符串末尾的任何ASCII都会破坏帧。因此,UTF-16文本格式(UTF-16 + BOM 0x01和UTF-16BE 0x02)完全不可靠,所有标记编写者都应该避免使用。除了纯ASCII之外,文本格式0x00不可靠。 UTF-8是赢家!]
如果你有一个编码终止字符串列表结构,就像那些为T
帧(TXXX
除外)指定的结构,那么简单的方法是在拆分之前解码它们U + 0000终结者:
def extractStrings(encoding_type, data):
chars = data.decode(encoding_type)
# chars is now a Unicode string, delimiter is always character U+0000
return chars.split(u'\0')
如果data
是一个完整的ID3框架,我恐怕你无法用一个split()
来处理它。除T
系列之外的帧包含编码终止字符串,仅ASCII终止字符串,二进制对象(没有终止)和整数字节/字值的混合。 APIC
就是这样,但是对于一般情况,您必须事先了解要解析的每个帧的结构,并逐个使用每个字段,随时手动查找每个终结符。
要在UTF-16编码数据中找到代码单元对齐的终结符而不会误解Āa
等,您可以使用正则表达式,例如:
ix= re.match('((?!\0\0)..)*', data, re.DOTALL).end()
s, remainder= data[:ix], data[ix+2:]
这真的不是很有趣 - ID3v2不是一个非常干净的格式。在我的头脑中,未经测试,这种事情就是我如何接近它:
p= FrameParser(data)
if frametype=='APIC':
encoding= p.encoding()
mimetype= p.string()
pictype= p.number(1)
desc= p.encodedstring()
img= p.binary()
class FrameParser(object):
def __init__(self, data):
self._data= data
self._ix= 0
self._encoding= 0
def encoding(self): # encoding byte - remember for later call to unicode()
self._encoding= self.number(1)
if not 0<=self._encoding<4:
raise ValueError('Unknown ID3 text encoding %r' % self._encoding)
return self._encoding
def number(self, nbytes= 1):
n= 0
for i in nbytes:
n*= 256
n+= ord(self._data[self._ix])
self._ix+= 1
return n
def binary(self): # the whole of the rest of the data, uninterpreted
s= self._data[self._ix:]
self._ix= len(self._data)
return s
def string(self): # non-encoded, maybe-terminated string
return self._string(0)
def encodedstring(self): # encoded, maybe-terminated string
return self._string(self._encoding)
def _string(self, encoding):
if encoding in (1, 2): # UTF-16 - look for double zero byte on code unit boundary
ix= re.match('((?!\0\0)..)*', self._data[self._ix:], re.DOTALL).end()
s= self._data[self._ix:self._ix+ix]
self._ix+= ix+2
else: # single-byte encoding - look for first zero byte
ix= self._data.find('\0', self._ix)
s= self._data[self._ix:self._ix+ix] if ix!=-1 else self._data[self._ix:]
self._ix= ix if ix!=-1 else len(self._data)
return s.decode(['windows-1252', 'utf-16', 'utf-16be', 'utf-8][encoding])
答案 1 :(得分:3)
为什么不首先解码字符串?
Python 2:
decoded = unicode(data, 'utf-8')
# or
decoded = unicode(data, 'utf-16')
Python 3:
decoded = str(data, 'utf-8')
# or
decoded = str(data, 'utf-16')
然后,您直接使用与编码无关的数据,并且分隔符始终为单个null。
答案 2 :(得分:0)
以下代码适用于我的要求,
def splitNullTerminatedEncStrings(self, data, encoding_type, no_of_splits):
data_dec = data.decode(encoding_type, 'ignore')
chunks = data_dec.split('\x00', no_of_splits)
enc_str_lst = []
for data_dec_seg in chunks[:-1]:
enc_str_lst.append(data_dec_seg.encode(encoding_type))
data_dec_chunks = '\x00'.join(chunks[:-1])
if(data_dec_chunks): data_dec_chunks += '\x00'
data_chunks = data_dec_chunks.encode(encoding_type)
data_chunks_len = len(data_chunks)
enc_str_lst.append(data[data_chunks_len:]) # last segment
return enc_str_lst