Инструменты для сверки с оригинальным байткодом
struct_diff сравнивает классы по составу и показывает, каких не хватает. Но он отвечает на вопрос «того же ли набора методов», а не «тот ли код». Оба вопроса приходится задавать: прыжки и звуки шагов были сломаны именно кодом, который компилировался без ошибок и состоял из правильных методов. javap_diff сравнивает инструкции построчно, вызывая javap на обоих деревьях классификации. Наивное сравнение текста здесь бесполезно: перекомпиляция перенумеровывает constant pool, поэтому почти каждая строка отличается номером #123, хотя код совпадает. Инструмент убирает номера пула, сдвиги смещений от ldc/ldc_w и форму инструкций, после чего остаётся реальная разница. На звуке инструмент показал, что все аудио-классы совпадают с оригиналом. Остатки объясняются версией компилятора, а не правками: javac 8 собирает конкатенацию строк через StringBuilder, javac 17 через invokedynamic, а $values() у enum появился в более новых версиях. ogg_duration читает частоту дискретизации из заголовка Vorbis и число отсчётов из granule position последней страницы OggS. Отсюда видно, что файл сам по себе короче или что его просто обрывают при воспроизведении, — вопрос, который иначе приходится решать на слух. Проверено, что все 1085 ogg совпадают с индексом по SHA-1.
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Report the real playback duration of Ogg Vorbis files, without decoding audio.
|
||||
|
||||
Reads the sample rate out of the Vorbis identification header and the total
|
||||
sample count from the granule position of the final OggS page, then divides.
|
||||
|
||||
This is how you tell a sound file that is genuinely shorter from a sound that
|
||||
merely stops early: the file either contains fewer samples or it does not.
|
||||
|
||||
Usage:
|
||||
ogg_duration.py <file.ogg> [file.ogg ...]
|
||||
"""
|
||||
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def info(path):
|
||||
with open(path, "rb") as fh:
|
||||
data = fh.read()
|
||||
|
||||
mark = data.find(b"\x01vorbis")
|
||||
if mark < 0:
|
||||
return None
|
||||
# packet type(1) + 'vorbis'(6) + version(4) + channels(1) -> rate(4)
|
||||
if len(data) < mark + 16:
|
||||
return None
|
||||
rate = struct.unpack_from("<I", data, mark + 12)[0]
|
||||
channels = data[mark + 11]
|
||||
|
||||
granule = 0
|
||||
pos = 0
|
||||
while True:
|
||||
pos = data.find(b"OggS", pos)
|
||||
if pos < 0:
|
||||
break
|
||||
if pos + 27 <= len(data):
|
||||
granule = struct.unpack_from("<q", data, pos + 6)[0]
|
||||
seg_count = data[pos + 26]
|
||||
table_end = pos + 27 + seg_count
|
||||
body = sum(data[pos + 27:table_end])
|
||||
pos = table_end + body
|
||||
else:
|
||||
pos += 4
|
||||
|
||||
seconds = granule / float(rate) if rate else 0.0
|
||||
return {
|
||||
"bytes": len(data),
|
||||
"rate": rate,
|
||||
"channels": channels,
|
||||
"samples": granule,
|
||||
"seconds": seconds,
|
||||
}
|
||||
|
||||
|
||||
def main(argv):
|
||||
for path in argv:
|
||||
got = info(path)
|
||||
if got is None:
|
||||
print("{:<52} not a Vorbis stream".format(path[-52:]))
|
||||
continue
|
||||
print("{:<52} {:>7.3f}s {:>6} Hz {:>2}ch {:>8} samples {:>6} B".format(
|
||||
path[-52:], got["seconds"], got["rate"], got["channels"],
|
||||
got["samples"], got["bytes"]))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
Reference in New Issue
Block a user