b6e847e981
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.
69 lines
1.9 KiB
Python
69 lines
1.9 KiB
Python
#!/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:])) |