Преобразование фрагментированного MP4 в MP4

Я пытаюсь очистить видеокадры с trafficview.org и не могу понять, как декодировать данные.

Я написал несколько строк кода на основе руководств по этому websocket_client для доступа к веб-сайту потоковой передачи и получения сообщения напрямую.

Я отслеживал сообщения, поступающие через вкладку сети в Chrome, а также копался в выводе из приведенного ниже кода и вполне уверен, что данные передаются в виде фрагментированного MP4. Ниже приведены первые 100 или около того байтов / сообщений:

б '\ xfa \ x00 \ x02 \ x86 \ xf1B \ xc0 \ x1e \ x00 \ x00 \ x00 \ x18ftypiso5 \ x00 \ x00 \ x02 \ x00iso6mp41 \ x00 \ x00 \ x02jmoov \ x00 \ x00 \ x00lmvhd \ x00 \ x00 \ x00 \ x00 \ xdb \ x7f \ xeb \ xb2 \ xdb \ x7f \ xeb \ xb2 \ x00 \ x00 \ x03 \ xe8 \ x00 \ x00 \ x00 \ x00 \ x00 \ x01 \ x00 \ x00 \ x01 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x01 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 '

В этом выводе много пар moof и mdat. Допустим, я позволил этому коду работать в течение 30 секунд, как я могу преобразовать эту необработанную строку байтов в файл mp4?

import json

from websocket import create_connection

url = 'wss://cctv.trafficview.org:8420/DDOT_CAPTOP_13.vod?progressive'

headers = json.dumps({
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'en-US,en;q=0.9',
    'Cache-Control': 'no-cache',
    'Connection': 'Upgrade',
    'Host': 'cctv.trafficview.org:8420',
    'Origin': 'https://trafficview.org',
    'Pragma': 'no-cache',
    'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits',
    'Sec-WebSocket-Key': 'FzWbrsoHFsJWzvWGJ04ffw==',
    'Sec-WebSocket-Version': '13',
    'Upgrade': 'websocket',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36',
})

ws = create_connection(url, headers=headers)

# Then send a message through the tunnel
ws.send('ping')

# Here you will view the message return from the tunnel
flag = 3000
output = b''
while flag > 0:
    output += ws.recv()
    flag -= 1

Обновление: я адаптировал некоторый код для переполнения стека, чтобы предположительно передать данные fmp4 и преобразовать их в фреймы. Чтобы добраться туда, я заметил, что первые 16 байтов вывода из веб-сокета не соответствуют другим файлам mp4, которые я проверил. Итак, я сначала обрезаю первые 16 байтов. Я также не знаю, как должен закончиться один из этих файлов, поэтому я обрезал его до последнего фрагмента файла.

Приведенный ниже код может нормально читать заголовок mp4 (также ниже), но не может декодировать ни один из байтов.

output = output[8:]

import re
moof_locs = [m.start() for m in re.finditer(b'moof', output)]

output = output[:moof_locs[-1]-1]

import subprocess as sp
import shlex

width, height = 640, 480

# FFmpeg input PIPE: WebM encoded data as stream of bytes.
# FFmpeg output PIPE: decoded video frames in BGR format.
process = sp.Popen(shlex.split('/usr/bin/ffmpeg -i pipe: -f hls -hls_segment_type fmp4 -c h264 -an -sn pipe:'), stdin=sp.PIPE, stdout=sp.PIPE, bufsize=10**8)
process.stdin.write(output)
process.stdin.close()
in_bytes = process.stdout.read(width * height * 3)
in_frame = (np.frombuffer(in_bytes, np.uint8).reshape([height, width, 3]))

Вывод из ffmpeg:

[mov,mp4,m4a,3gp,3g2,mj2 @ 0x994600] Could not find codec parameters for stream 0 (Video: h264 (avc1 / 0x31637661), none, 640x480): unspecified pixel format
Consider increasing the value for the 'analyzeduration' and 'probesize' options
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'pipe:':
  Metadata:
    major_brand     : iso5
    minor_version   : 512
    compatible_brands: iso6mp41
    creation_time   : 2020-09-11T13:40:21.000000Z
  Duration: N/A, bitrate: N/A
    Stream #0:0(und): Video: h264 (avc1 / 0x31637661), none, 640x480, 1k tbr, 1k tbn, 2k tbc (default)
    Metadata:
      creation_time   : 2020-09-11T13:40:21.000000Z
      encoder         : EvoStream Media Server
Stream mapping:
  Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))
Finishing stream 0:0 without any data written to it.
Nothing was written into output file 0 (pipe:), because at least one of its streams received no packets.
frame=    0 fps=0.0 q=0.0 Lsize=       0kB time=-577014:32:22.77 bitrate=  -0.0kbits/s speed=N/A    
video:0kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
Output file is empty, nothing was encoded (check -ss / -t / -frames parameters if used)

Обновление 2:

Изучив поток, поступающий из веб-сокета, я понял, что каждое сообщение начинается с определенного целого числа, которое определено в коде javascript из trafficview. Порядок этих кодов ВСЕГДА один и тот же, они бывают следующим образом:

Header MOOV (250)
    PBT Begin (249)
        Video Buffer (252)
        Header MOOF (251)
        Header MOOF (251)
        Header MOOF (251)
        Header MDAT (254)
    PBT End (255)

    PBT Begin (249)
    Continues Forever

Некоторые из этих тегов всегда одинаковы, например, 249 сообщений всегда f900 0000, а 255 сообщений всегда ff00 0000.

Я предполагаю, что сообщения 249 и 255 обычно не находятся во фрагментированном потоке mp4 или hls, и поэтому я думаю, что мне нужно использовать эту информацию тега, чтобы создать правильный формат файла с нуля.


person Will.Evo    schedule 09.09.2020    source источник
comment
Вы можете попытаться реконструировать код javascript, который обрабатывает данные веб-сокета. Запустите trafficview.org/js/040f112.js через средство украшения (например, codebeautify.org/jsviewer), чтобы сделать его более читабельным, а затем взгляните на функции EvoWsPlayer.prototype.play и EvoPlayer.prototype.parseData .   -  person mcernak    schedule 12.09.2020
comment
Спасибо что подметил это. Похоже, что сценарий был написан, а затем перемешан специально, чтобы сделать его реверс-инжиниринг практически невозможным (имеет смысл). Я также не знаю JavaScript, поэтому это усложняет задачу.   -  person Will.Evo    schedule 14.09.2020


Ответы (1)


ws = create_connection(url, headers=headers)
# Then send a message through the tunnel
ws.send('ping')

start = timeit.default_timer()
flag = True
output = []
while flag:
    output.append(ws.recv())
    if timeit.default_timer() - start > 90:
        flag = False

result = output[0][8:]

for msg in output[1:]:
    if msg[0] == 249:
        moofmdat = b''
        moof = b''
        continue

    if msg[0] == 252:
        vidbuf = msg[4:]

    if msg[0] == 251:
        moof += msg[4:]

    if msg[0] == 254:
        mdat = msg[4:]

    if msg[0] == 255:
        moofmdat += moof
        moofmdat += mdat
        moofmdat += vidbuf
        result += moofmdat

with open('test.mp4', 'wb') as file:
    file.write(result)

Догадаться. Заголовок MOOV содержит 8 байтов ненужной информации, которую необходимо удалить. Каждое дополнительное сообщение (кроме PBT_Begin и PBT_End) содержит 4 байта специфических данных игрока. Просто нужно было очистить каждое сообщение и разместить в правильном порядке. Затем сохраните необработанные байты как mp4 и вуаля, видео, которое воспроизводится в vlc.

person Will.Evo    schedule 15.09.2020