IPB

Добро пожаловать, гость ( Вход | Регистрация )

История благодарностей участнику Lumis ::: Спасибо сказали: 1 раз(а)
Дата поста: В теме: За сообщение: Спасибо сказали:
Вчера, 22:52 Конвертор видео ГЭГ 2 DV -> OGM
Всем привет. Коллеги написали прекрасный распаковщик файлов ресурсов, за что им громадное спасибо.
А у меня получилось разобрать странный формат их видеофайлов ".avi", которые никак на традиционные AVI от майков не похожи.

По предложению ув. -=CHE@TER=- выкладываю вариант перегонки "DV!!" или "dvbang" в контейнер OGM без переупаковки потока, без добавления таймкодов и пр. Конвертор грязноват: вопроизводится нормально, но ffmpeg генерирует много ошибок "[aist#0:0/vorbis @ 000002192aad3680] [dec:vorbis @ 000002192aa71740] Error submitting packet to decoder: Invalid data found when processing input". Есть также вариант переупаковки с помощью ffmpeg без переконвертации для добавления таймкодов.

Код в значительной степени сгенерирован AI. Лицензия - MIT.
Сборка макс. компактного бинарника в VC Express:
CODE
cl /TC /O1 /Os /GS- /Gy /GL dvbang2ogv.c /link /LTCG /OPT:REF /OPT:ICF /SUBSYSTEM:CONSOLE


CODE
Copyright 2026 Lumis

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


[codebox]/* dvbang2ogv.c
* ANSI C / C89, Visual Studio 2008 friendly
* License: MIT, Copyright 2026 Lumis
*
* Converts "DV!!" .dv files (non-standard container) into Ogg/OGM (.ogv) without
* using ffmpeg for muxing. Video is MPEG-4 Part 2 elementary stream kept byte-exact.
* Audio is Ogg/Vorbis pages copied byte-exact (no re-encode).
*
* Matches the latest Python + C++ variants:
* - DV!! header: 19 LE u32 (76 bytes), hdr[0]='DV!!', hdr[4]=w, hdr[5]=h,
* hdr[7]=packet_count N, hdr[9]=audio_preroll_bytes M.
* - Table: N entries * 20 bytes (5 LE u32 each):
* size = entry0 & 0x7fffffff
* audio_bytes = entry[3]
* - Data: preroll (M bytes) + N packets:
* video += packet[0 : size-audio_bytes]
* audio += packet[size-audio_bytes : size]
*
* OGV/OGM mux:
* - Copy audio BOS page unchanged
* - Write video BOS (0x01 + ogm stream_header, subtype "XVID")
* - Write video comment (0x03 + vorbis-style vendor comment, no user comments)
* - Copy remaining Vorbis header pages unchanged (until 3 Vorbis packets end)
* - Interleave:
* at = audio_page.granule / sample_rate
* emit video frames while ((v_i+1)/fps) <= at + (0.5/fps)
* then copy audio page
* Finally flush remaining video frames.
*
* Determinism:
* - video serial hardcoded to 0xC0DEC0DE; if equals audio serial => xor with 0xFFFFFFFF.
*
* Optional:
* --validate : runs "ffmpeg ... -f null -" via system() (disabled by default)
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* VS2008: snprintf is _snprintf */
#ifndef snprintf
#define snprintf _snprintf
#endif

/* ---------- Fixed constants ---------- */
#define MAGIC_DV_LE 0x21215644UL /* "DV!!" little-endian */
#define VIDEO_SERIAL_FIXED 0xC0DEC0DEUL

/* ---------- Basic types ---------- */
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32; /* Win32: 32-bit */
typedef unsigned __int64 u64;

typedef struct {
u8* p;
u32 len;
u32 cap;
} Buf;

typedef struct {
u8 header_type;
u64 granule;
u32 serial;
u32 seq;
u8* segtable;
u32 segtable_len;
u8* body;
u32 body_len;
u8* bytes;
u32 bytes_len;
} OggPage;

typedef struct { u8* p; u32 len; } Span;

/* ---------- Errors ---------- */
static void die(const char* msg) {
fprintf(stderr, "ERROR: %s\n", msg);
exit(2);
}
static void die2(const char* msg, const char* more) {
fprintf(stderr, "ERROR: %s%s\n", msg, more ? more : "");
exit(2);
}

/* ---------- LE helpers ---------- */
static u32 rd32le(const u8* p) {
return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24);
}
static u64 rd64le(const u8* p) {
u64 v;
v = (u64)p[0];
v |= (u64)p[1] << 8;
v |= (u64)p[2] << 16;
v |= (u64)p[3] << 24;
v |= (u64)p[4] << 32;
v |= (u64)p[5] << 40;
v |= (u64)p[6] << 48;
v |= (u64)p[7] << 56;
return v;
}
static void wr32le(u8* p, u32 v) {
p[0] = (u8)(v & 0xFF);
p[1] = (u8)((v >> 8) & 0xFF);
p[2] = (u8)((v >> 16) & 0xFF);
p[3] = (u8)((v >> 24) & 0xFF);
}
static void wr64le(u8* p, u64 v) {
p[0] = (u8)(v & 0xFF);
p[1] = (u8)((v >> 8) & 0xFF);
p[2] = (u8)((v >> 16) & 0xFF);
p[3] = (u8)((v >> 24) & 0xFF);
p[4] = (u8)((v >> 32) & 0xFF);
p[5] = (u8)((v >> 40) & 0xFF);
p[6] = (u8)((v >> 48) & 0xFF);
p[7] = (u8)((v >> 56) & 0xFF);
}

/* ---------- Buf ---------- */
static void buf_init(Buf* b ) { b->p = 0; b->len = 0; b->cap = 0; }

static void buf_reserve(Buf* b, u32 need) {
u32 ncap;
if (need <= b->cap) return;
ncap = b->cap ? (b->cap + (b->cap >> 1) + 64) : 256;
if (ncap < need) ncap = need;
b->p = (u8*)realloc(b->p, ncap);
if (!b->p) die("Out of memory");
b->cap = ncap;
}

static void buf_append(Buf* b, const u8* data, u32 n) {
if (n == 0) return;
buf_reserve(b, b->len + n);
memcpy(b->p + b->len, data, n);
b->len += n;
}

static void buf_append_u8(Buf* b, u8 v) {
buf_reserve(b, b->len + 1);
b->p[b->len++] = v;
}

static void buf_free(Buf* b ) {
if (b->p) free(b->p);
b->p = 0; b->len = 0; b->cap = 0;
}

/* ---------- File I/O ---------- */
static u8* read_file_all(const char* path, u32* out_len) {
FILE* f;
long sz;
u8* data;

f = fopen(path, "rb");
if (!f) return 0;
fseek(f, 0, SEEK_END);
sz = ftell(f);
if (sz < 0) { fclose(f); return 0; }
fseek(f, 0, SEEK_SET);

data = (u8*)malloc((u32)sz);
if (!data) { fclose(f); return 0; }

if (sz > 0 && fread(data, 1, (u32)sz, f) != (u32)sz) {
free(data);
fclose(f);
return 0;
}
fclose(f);
*out_len = (u32)sz;
return data;
}

/* ---------- Ogg CRC ---------- */
static u32 CRC_TABLE[256];

static void crc_table_init(void) {
u32 poly, i, j, r;
poly = 0x04C11DB7UL;
for (i = 0; i < 256; i++) {
r = i << 24;
for (j = 0; j < 8; j++) {
if (r & 0x80000000UL) r = (r << 1) ^ poly;
else r <<= 1;
}
CRC_TABLE[i] = r;
}
}

static u32 ogg_crc(const u8* page, u32 len) {
u32 crc, i;
crc = 0;
for (i = 0; i < len; i++) {
u8 b = page[i];
crc = (crc << 8) ^ CRC_TABLE[((crc >> 24) & 0xFF) ^ b];
}
return crc;
}

/* ---------- Ogg page builder ----------
* Builds one page containing pkt_count packets (commonly 1 packet per page here).
*/
static void ogg_build_page(Buf* out,
const u8* const* pkts, const u32* pkt_lens, u32 pkt_count,
u32 serial, u32 seq, u64 granule, u8 header_type)
{
u8 segs[255];
u32 seg_count, i;
Buf body;
Buf page;
u8 hdr[27];
u32 crc;

seg_count = 0;
buf_init(&body);
buf_init(&page);

/* Build body and lacing values */
for (i = 0; i < pkt_count; i++) {
u32 n;
buf_append(&body, pkts[i], pkt_lens[i]);
n = pkt_lens[i];
while (n >= 255) {
if (seg_count >= 255) die("Too many segments in one Ogg page");
segs[seg_count++] = 255;
n -= 255;
}
if (seg_count >= 255) die("Too many segments in one Ogg page");
segs[seg_count++] = (u8)n;
}

/* Header */
memset(hdr, 0, sizeof(hdr));
hdr[0] = 'O'; hdr[1] = 'g'; hdr[2] = 'g'; hdr[3] = 'S';
hdr[4] = 0; /* version */
hdr[5] = header_type;
wr64le(hdr + 6, granule);
wr32le(hdr + 14, serial);
wr32le(hdr + 18, seq);
wr32le(hdr + 22, 0); /* CRC placeholder */
hdr[26] = (u8)seg_count;

buf_append(&page, hdr, 27);
buf_append(&page, segs, seg_count);
buf_append(&page, body.p, body.len);

/* Patch CRC */
crc = ogg_crc(page.p, page.len);
wr32le(page.p + 22, crc);

buf_append(out, page.p, page.len);

buf_free(&page);
buf_free(&body);
}

/* ---------- Ogg page parser (minimal) ---------- */
static OggPage* ogg_parse_pages(const u8* data, u32 len, u32* out_count) {
OggPage* pages;
u32 cap, count, i;

cap = 64;
count = 0;
pages = (OggPage*)calloc(cap, sizeof(OggPage));
if (!pages) die("Out of memory");

i = 0;
while (1) {
u32 j, k, body_len, body_off, seg_off;
int found;
u8 nseg;
u8 header_type;
u64 granule;
u32 serial, seq;

found = 0;
for (j = i; j + 4 <= len; j++) {
if (data[j]=='O' && data[j+1]=='g' && data[j+2]=='g' && data[j+3]=='S') {
found = 1;
break;
}
}
if (!found) break;
if (j + 27 > len) break;

header_type = data[j + 5];
granule = rd64le(data + j + 6);
serial = rd32le(data + j + 14);
seq = rd32le(data + j + 18);
nseg = data[j + 26];

seg_off = j + 27;
body_off = seg_off + (u32)nseg;
if (body_off > len) break;

body_len = 0;
for (k = 0; k < (u32)nseg; k++) body_len += data[seg_off + k];
if (body_off + body_len > len) break;

if (count >= cap) {
cap = cap + cap/2 + 32;
pages = (OggPage*)realloc(pages, cap * sizeof(OggPage));
if (!pages) die("Out of memory");
}

pages[count].header_type = header_type;
pages[count].granule = granule;
pages[count].serial = serial;
pages[count].seq = seq;

pages[count].segtable_len = (u32)nseg;
pages[count].segtable = (u8*)malloc((u32)nseg);
if (!pages[count].segtable) die("Out of memory");
memcpy(pages[count].segtable, data + seg_off, (u32)nseg);

pages[count].body_len = body_len;
pages[count].body = (u8*)malloc(body_len);
if (!pages[count].body) die("Out of memory");
memcpy(pages[count].body, data + body_off, body_len);

pages[count].bytes_len = (body_off + body_len) - j;
pages[count].bytes = (u8*)malloc(pages[count].bytes_len);
if (!pages[count].bytes) die("Out of memory");
memcpy(pages[count].bytes, data + j, pages[count].bytes_len);

count++;
i = body_off + body_len;
}

*out_count = count;
return pages;
}

static void ogg_free_pages(OggPage* pages, u32 count) {
u32 i;
for (i = 0; i < count; i++) {
if (pages[i].segtable) free(pages[i].segtable);
if (pages[i].body) free(pages[i].body);
if (pages[i].bytes) free(pages[i].bytes);
}
free(pages);
}

/* ---------- Vorbis helpers ---------- */
static u32 vorbis_detect_sample_rate(const OggPage* pages, u32 count) {
Buf cur;
u32 i, sidx;

buf_init(&cur);
for (i = 0; i < count; i++) {
u32 off = 0;
for (sidx = 0; sidx < pages[i].segtable_len; sidx++) {
u8 s = pages[i].segtable[sidx];
buf_append(&cur, pages[i].body + off, (u32)s);
off += (u32)s;

if (s < 255) {
if (cur.len >= 16 &&
cur.p[0] == 0x01 &&
cur.p[1]=='v' && cur.p[2]=='o' && cur.p[3]=='r' &&
cur.p[4]=='b' && cur.p[5]=='i' && cur.p[6]=='s')
{
u32 sr = rd32le(cur.p + 12);
buf_free(&cur);
return sr;
}
buf_free(&cur);
return 0;
}
}
}
buf_free(&cur);
return 0;
}

static u32 vorbis_header_end_page_index(const OggPage* pages, u32 count) {
u32 pkt_count, i, sidx;
pkt_count = 0;
for (i = 0; i < count; i++) {
for (sidx = 0; sidx < pages[i].segtable_len; sidx++) {
if (pages[i].segtable[sidx] < 255) {
pkt_count++;
if (pkt_count >= 3) return i;
}
}
}
return (count ? (count - 1) : 0);
}

/* ---------- DV!! extraction ---------- */
static void dvbang_extract(const u8* dv, u32 dv_len,
u32* out_w, u32* out_h,
u8** out_video, u32* out_video_len,
u8** out_audio, u32* out_audio_len,
u32* out_packet_count)
{
u32 hdr[19];
u32 i;
u32 N, M;
u32 table_off, table_len, data_off;
u32 pos;
Buf video, audio;

if (dv_len < 76) die("Input too small for DV!! header");

for (i = 0; i < 19; i++) hdr[i] = rd32le(dv + i*4);

if (hdr[0] != MAGIC_DV_LE) die("Bad DV!! signature");

*out_w = hdr[4];
*out_h = hdr[5];
N = hdr[7];
M = hdr[9];
*out_packet_count = N;

table_off = 76;
table_len = N * 20UL;
if (dv_len < table_off + table_len) die("Truncated: packet table");

data_off = table_off + table_len;
if (dv_len < data_off + M) die("Truncated: preroll region");

buf_init(&video);
buf_init(&audio);

/* audio begins with preroll */
buf_append(&audio, dv + data_off, M);

pos = data_off + M;
for (i = 0; i < N; i++) {
const u8* ent;
u32 size_flags, size, audio_bytes;

ent = dv + (table_off + i*20UL);
size_flags = rd32le(ent);
size = size_flags & 0x7FFFFFFFUL;
audio_bytes = rd32le(ent + 12);

if (pos + size > dv_len) die("Truncated: packet region");
if (audio_bytes > size) die("Corrupt: audio_bytes > size");

if (audio_bytes) {
u32 vlen = size - audio_bytes;
buf_append(&video, dv + pos, vlen);
buf_append(&audio, dv + pos + vlen, audio_bytes);
} else {
buf_append(&video, dv + pos, size);
}
pos += size;
}

*out_video = video.p; *out_video_len = video.len;
*out_audio = audio.p; *out_audio_len = audio.len;
}

/* ---------- MPEG-4 Part 2 frame splitting ---------- */
static Span* mpeg4_split_vop_frames(const u8* es, u32 es_len, u32* out_count) {
const u8 vop0 = 0x00, vop1 = 0x00, vop2 = 0x01, vop3 = 0xB6;
u32* idxs;
u32 cap, count, i;

Span* frames;
u32 header_len;
u8* header;

cap = 1024;
count = 0;
idxs = (u32*)malloc(cap * sizeof(u32));
if (!idxs) die("Out of memory");

i = 0;
while (i + 4 <= es_len) {
u32 j;
int found;
found = 0;
for (j = i; j + 4 <= es_len; j++) {
if (es[j]==vop0 && es[j+1]==vop1 && es[j+2]==vop2 && es[j+3]==vop3) {
found = 1;
break;
}
}
if (!found) break;

if (count >= cap) {
cap = cap + cap/2 + 256;
idxs = (u32*)realloc(idxs, cap * sizeof(u32));
if (!idxs) die("Out of memory");
}
idxs[count++] = j;
i = j + 4;
}

if (count == 0) {
free(idxs);
die("No MPEG-4 VOP start codes (00 00 01 B6) found");
}

header_len = idxs[0];
header = 0;
if (header_len) {
header = (u8*)malloc(header_len);
if (!header) die("Out of memory");
memcpy(header, es, header_len);
}

frames = (Span*)calloc(count, sizeof(Span));
if (!frames) die("Out of memory");

for (i = 0; i < count; i++) {
u32 start, end, flen;
start = idxs[i];
end = (i + 1 < count) ? idxs[i+1] : es_len;
flen = end - start;

if (i == 0) {
u32 total = header_len + flen;
frames[i].p = (u8*)malloc(total);
if (!frames[i].p) die("Out of memory");
if (header_len) memcpy(frames[i].p, header, header_len);
memcpy(frames[i].p + header_len, es + start, flen);
frames[i].len = total;
} else {
frames[i].p = (u8*)malloc(flen);
if (!frames[i].p) die("Out of memory");
memcpy(frames[i].p, es + start, flen);
frames[i].len = flen;
}
}

if (header) free(header);
free(idxs);
*out_count = count;
return frames;
}

static void free_frames(Span* frames, u32 count) {
u32 i;
for (i = 0; i < count; i++) if (frames[i].p) free(frames[i].p);
free(frames);
}

/* ---------- OGM packet builders ---------- */
static void ogm_build_stream_header_video(u8 out52[52], int w, int h, int fps) {
/* See ogmtools ogmstreams.h layout; kept identical to prior variants. */
double tu_d;
__int64 time_unit;
__int64 samples_per_unit;
u32 default_len;
u32 buffersize;
u16 bits_per_sample;
u16 padding;

memset(out52, 0, 52);
memcpy(out52 + 0, "video", 5);
memcpy(out52 + 8, "XVID", 4);
wr32le(out52 + 12, 52);

tu_d = 10000000.0 / (double)fps;
time_unit = (__int64)(tu_d + 0.5);
samples_per_unit = 1;
default_len = 1;
buffersize = 0;
bits_per_sample = 0;
padding = 0;

wr64le(out52 + 16, (u64)time_unit);
wr64le(out52 + 24, (u64)samples_per_unit);
wr32le(out52 + 32, default_len);
wr32le(out52 + 36, buffersize);
out52[40] = (u8)(bits_per_sample & 0xFF);
out52[41] = (u8)((bits_per_sample >> 8) & 0xFF);
wr32le(out52 + 42, (u32)w);
wr32le(out52 + 46, (u32)h);
out52[50] = (u8)(padding & 0xFF);
out52[51] = (u8)((padding >> 8) & 0xFF);
}

static void ogm_make_video_header_pkt(Buf* pkt, int w, int h, int fps) {
u8 sh[52];
buf_init(pkt);
ogm_build_stream_header_video(sh, w, h, fps);
buf_append_u8(pkt, 0x01);
buf_append(pkt, sh, 52);
}

static void ogm_make_video_comment_pkt(Buf* pkt) {
const char* vendor = "dvbang";
u32 vlen;
u8 tmp[4];

vlen = (u32)strlen(vendor);
buf_init(pkt);
buf_append_u8(pkt, 0x03);
wr32le(tmp, vlen);
buf_append(pkt, tmp, 4);
buf_append(pkt, (const u8*)vendor, vlen);
wr32le(tmp, 0);
buf_append(pkt, tmp, 4);
}

static void ogm_make_video_data_pkt(Buf* pkt, const u8* frame, u32 frame_len) {
u8 flags;
buf_init(pkt);

/* lenbytes=2 (bit1 set => 0x80), keyframe=1 (0x08) */
flags = (u8)(0x80 | 0x08);

buf_append_u8(pkt, flags);
buf_append_u8(pkt, 1); /* duration samples=1 (LE16) */
buf_append_u8(pkt, 0);
buf_append(pkt, frame, frame_len);
}

/* ---------- Mux (writes .ogv) ---------- */
static void mux_ogv(const char* out_path,
const OggPage* audio_pages, u32 audio_page_count,
u32 sample_rate,
int w, int h, int fps,
const Span* frames, u32 frame_count)
{
FILE* fo;
Buf out;
u32 audio_serial, video_serial, hdr_end;
u32 vseq, v_i;
double fps_d, half_frame;

Buf vh, vc, vp;
const u8* pkts1[1];
u32 lens1[1];

u32 ai;

if (audio_page_count == 0) die("No audio pages");
audio_serial = audio_pages[0].serial;

video_serial = VIDEO_SERIAL_FIXED;
if (video_serial == audio_serial) video_serial ^= 0xFFFFFFFFUL;

hdr_end = vorbis_header_end_page_index(audio_pages, audio_page_count);

buf_init(&out);

/* audio BOS copied */
buf_append(&out, audio_pages[0].bytes, audio_pages[0].bytes_len);

/* video BOS */
ogm_make_video_header_pkt(&vh, w, h, fps);
pkts1[0] = vh.p; lens1[0] = vh.len;
ogg_build_page(&out, pkts1, lens1, 1, video_serial, 0, 0, 0x02);
buf_free(&vh);

vseq = 1;

/* video comment */
ogm_make_video_comment_pkt(&vc);
pkts1[0] = vc.p; lens1[0] = vc.len;
ogg_build_page(&out, pkts1, lens1, 1, video_serial, vseq++, 0, 0x00);
buf_free(&vc);

/* remaining audio header pages unchanged */
for (ai = 1; ai <= hdr_end && ai < audio_page_count; ai++) {
buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len);
}

fps_d = (double)fps;
half_frame = 0.5 / fps_d;
v_i = 0;

/* interleave */
for (ai = hdr_end + 1; ai < audio_page_count; ai++) {
double at;
at = 0.0;
if (sample_rate) at = (double)audio_pages[ai].granule / (double)sample_rate;

while (v_i < frame_count) {
double vt_next;
vt_next = (double)(v_i + 1) / fps_d;
if (vt_next <= at + half_frame) {
u64 gran;
u8 header_type;

ogm_make_video_data_pkt(&vp, frames[v_i].p, frames[v_i].len);
pkts1[0] = vp.p; lens1[0] = vp.len;
gran = (u64)(v_i + 1);
header_type = (u8)((v_i + 1 == frame_count) ? 0x04 : 0x00);
ogg_build_page(&out, pkts1, lens1, 1, video_serial, vseq++, gran, header_type);
buf_free(&vp);

v_i++;
} else {
break;
}
}

/* copy audio page */
buf_append(&out, audio_pages[ai].bytes, audio_pages[ai].bytes_len);
}

/* flush remaining video */
while (v_i < frame_count) {
u64 gran;
u8 header_type;

ogm_make_video_data_pkt(&vp, frames[v_i].p, frames[v_i].len);
pkts1[0] = vp.p; lens1[0] = vp.len;
gran = (u64)(v_i + 1);
header_type = (u8)((v_i + 1 == frame_count) ? 0x04 : 0x00);
ogg_build_page(&out, pkts1, lens1, 1, video_serial, vseq++, gran, header_type);
buf_free(&vp);

v_i++;
}

/* write output */
fo = fopen(out_path, "wb");
if (!fo) die("Cannot open output file");
if (out.len && fwrite(out.p, 1, out.len, fo) != out.len) {
fclose(fo);
die("Write failed");
}
fclose(fo);

buf_free(&out);
}

/* ---------- CLI helpers ---------- */
static int streq(const char* a, const char* b ) { return strcmp(a,b ) == 0; }

static void usage(const char* exe) {
fprintf(stderr,
"Usage:\n %s <input.dv> [output.ogv] [--validate]\n\n"
"Options:\n"
" --validate run ffmpeg decode test (optional; ffmpeg must be in PATH)\n"
" -h,--help show help\n", exe);
}

static void make_default_out(const char* in, char* out, u32 outsz) {
const char* dot;
u32 n;

dot = strrchr(in, '.');
if (!dot) {
snprintf(out, outsz, "%s.ogv", in);
return;
}
n = (u32)(dot - in);
if (n + 4 + 1 >= outsz) die("Output path too long");
memcpy(out, in, n);
memcpy(out + n, ".ogv", 5);
}

int main(int argc, char** argv) {
const char* in_path;
const char* out_arg;
char out_path[1024];
int validate;
int fps;

u8* dv;
u32 dv_len;

u32 w, h, packet_count;
u8* video_es; u32 video_es_len;
u8* audio_ogg; u32 audio_ogg_len;

OggPage* audio_pages; u32 audio_page_count;
u32 sample_rate;

Span* frames; u32 frame_count;

int i;
int positional;

/* defaults */
in_path = 0;
out_arg = 0;
validate = 0;
fps = 25; /* fixed as in your tested files */

if (argc < 2) { usage(argv[0]); return 2; }

positional = 0;
for (i = 1; i < argc; i++) {
const char* a = argv[i];

if (streq(a, "-h") || streq(a, "--help")) { usage(argv[0]); return 0; }
if (streq(a, "--validate")) { validate = 1; continue; }

if (a[0] == '-') {
fprintf(stderr, "Unknown option: %s\n\n", a);
usage(argv[0]);
return 2;
}

if (positional == 0) { in_path = a; positional++; continue; }
if (positional == 1) { out_arg = a; positional++; continue; }

fprintf(stderr, "Too many arguments.\n\n");
usage(argv[0]);
return 2;
}

if (!in_path) { usage(argv[0]); return 2; }
if (out_arg) {
strncpy(out_path, out_arg, sizeof(out_path)-1);
out_path[sizeof(out_path)-1] = 0;
} else {
make_default_out(in_path, out_path, (u32)sizeof(out_path));
}

crc_table_init();

dv = read_file_all(in_path, &dv_len);
if (!dv) die2("Cannot read input file: ", in_path);

/* Extract */
w = h = packet_count = 0;
video_es = 0; video_es_len = 0;
audio_ogg = 0; audio_ogg_len = 0;

dvbang_extract(dv, dv_len, &w, &h,
&video_es, &video_es_len,
&audio_ogg, &audio_ogg_len,
&packet_count);

/* Parse audio pages */
audio_pages = 0;
audio_page_count = 0;
audio_pages = ogg_parse_pages(audio_ogg, audio_ogg_len, &audio_page_count);
if (!audio_pages || audio_page_count == 0) die("Audio parse failed: no Ogg pages");

/* Sample rate */
sample_rate = vorbis_detect_sample_rate(audio_pages, audio_page_count);
if (sample_rate == 0) {
fprintf(stderr, "WARNING: could not detect Vorbis sample rate; assuming 44100.\n");
sample_rate = 44100;
}

/* Split frames */
frames = mpeg4_split_vop_frames(video_es, video_es_len, &frame_count);

/* Mux */
mux_ogv(out_path, audio_pages, audio_page_count, sample_rate,
(int)w, (int)h, fps, frames, frame_count);

printf("ogv: %s\n", out_path);
printf("video: MPEG-4 Part 2 (%lux%lu, frames=%lu, fps=%d)\n", w, h, frame_count, fps);
printf("audio: Vorbis (sr=%lu, pages=%lu)\n", sample_rate, audio_page_count);

/* Optional validation */
if (validate) {
char cmd[1400];
int rc;
snprintf(cmd, sizeof(cmd),
"ffmpeg -hide_banner -loglevel error -i \"%s\" -f null -", out_path);
rc = system(cmd);
if (rc != 0) {
fprintf(stderr, "WARNING: ffmpeg validation failed (ffmpeg missing or decode errors).\n");
}
}

/* cleanup */
if (dv) free(dv);
if (video_es) free(video_es);
if (audio_ogg) free(audio_ogg);
if (audio_pages) ogg_free_pages(audio_pages, audio_page_count);
if (frames) free_frames(frames, frame_count);

return 0;
}
[/codebox]

Сразу же прикладываю вдогонку конвертор в более практичные хорошо перематываемые файлы (MKV + видеопоток MPEG-4 с добавленными таймкодами).
Требует python + ffmpeg.

Комментарии приветствуются.
-=CHE@TER=-,

Упрощённая версия Сейчас: 3rd March 2026 - 04:35