Guest User

DV to OGM video converter C++

a guest
Feb 28th, 2026
53
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 15.36 KB | None | 0 0
  1. #include <cstdint>
  2. #include <cstring>
  3. #include <cmath>
  4. #include <fstream>
  5. #include <iostream>
  6. #include <stdexcept>
  7. #include <string>
  8. #include <vector>
  9. #include <filesystem>
  10. #include <algorithm>
  11.  
  12. using namespace std;
  13. using Path = std::filesystem::path;
  14.  
  15. static const uint32_t MAGIC_DV = 0x21215644u; // "DV!!" little-endian
  16.  
  17. static void eprint(const string& s){ cerr << s << "\n"; }
  18.  
  19. static void usage(const char* argv0){
  20.   cerr << "Usage:\n  " << argv0 << " <input.dv> [output.ogv] [--validate]\n\n"
  21.        << "Converts DV!! (non-standard) .dv into Ogg/OGM .ogv without ffmpeg muxing.\n"
  22.        << "Options:\n"
  23.        << "  --validate   Run ffmpeg decode test on output (disabled by default)\n"
  24.        << "  -h, --help   Show this help\n";
  25. }
  26.  
  27. // ---------------- Ogg CRC ----------------
  28. static vector<uint32_t> make_crc_table(){
  29.   const uint32_t poly = 0x04C11DB7u;
  30.   vector<uint32_t> t(256);
  31.   for(uint32_t i=0;i<256;i++){
  32.     uint32_t r = i << 24;
  33.     for(int j=0;j<8;j++) r = (r & 0x80000000u) ? ((r<<1) ^ poly) : (r<<1);
  34.     t[i]=r;
  35.   }
  36.   return t;
  37. }
  38. static const vector<uint32_t> CRC_TABLE = make_crc_table();
  39.  
  40. static uint32_t ogg_crc(const vector<uint8_t>& page){
  41.   uint32_t crc=0;
  42.   for(uint8_t b: page){
  43.     crc = (crc<<8) ^ CRC_TABLE[((crc>>24)&0xFFu) ^ b];
  44.   }
  45.   return crc;
  46. }
  47.  
  48. static void append_le32(vector<uint8_t>& v, uint32_t x){
  49.   v.push_back((uint8_t)(x & 0xFF));
  50.   v.push_back((uint8_t)((x>>8)&0xFF));
  51.   v.push_back((uint8_t)((x>>16)&0xFF));
  52.   v.push_back((uint8_t)((x>>24)&0xFF));
  53. }
  54. static void append_le64(vector<uint8_t>& v, uint64_t x){
  55.   for(int i=0;i<8;i++) v.push_back((uint8_t)((x>>(8*i))&0xFF));
  56. }
  57.  
  58. static vector<uint8_t> build_ogg_page(const vector<vector<uint8_t>>& packets,
  59.                                      uint32_t serial, uint32_t seq,
  60.                                      uint64_t granule, uint8_t header_type){
  61.   vector<uint8_t> segs;
  62.   vector<uint8_t> body;
  63.  
  64.   for(const auto& pkt: packets){
  65.     body.insert(body.end(), pkt.begin(), pkt.end());
  66.     size_t n = pkt.size();
  67.     while(n >= 255){ segs.push_back(255); n -= 255; }
  68.     segs.push_back((uint8_t)n);
  69.   }
  70.   if(segs.size() > 255) throw runtime_error("Too many segments in one page");
  71.  
  72.   vector<uint8_t> header;
  73.   header.insert(header.end(), {'O','g','g','S'});
  74.   header.push_back(0); // version
  75.   header.push_back(header_type);
  76.   append_le64(header, granule);
  77.   append_le32(header, serial);
  78.   append_le32(header, seq);
  79.   append_le32(header, 0); // CRC placeholder
  80.   header.push_back((uint8_t)segs.size());
  81.   header.insert(header.end(), segs.begin(), segs.end());
  82.  
  83.   vector<uint8_t> page = header;
  84.   page.insert(page.end(), body.begin(), body.end());
  85.  
  86.   uint32_t crc = ogg_crc(page);
  87.   page[22] = (uint8_t)(crc & 0xFF);
  88.   page[23] = (uint8_t)((crc>>8)&0xFF);
  89.   page[24] = (uint8_t)((crc>>16)&0xFF);
  90.   page[25] = (uint8_t)((crc>>24)&0xFF);
  91.   return page;
  92. }
  93.  
  94. // ------------- Ogg page parsing (audio pages copied as-is) -------------
  95. struct OggPage {
  96.   uint8_t header_type;
  97.   uint64_t granule;
  98.   uint32_t serial;
  99.   uint32_t seq;
  100.   vector<uint8_t> segtable;
  101.   vector<uint8_t> body;
  102.   vector<uint8_t> bytes; // full original page bytes
  103. };
  104.  
  105. static vector<OggPage> parse_ogg_pages(const vector<uint8_t>& data){
  106.   vector<OggPage> pages;
  107.   size_t i=0;
  108.   const uint8_t sig[4] = {'O','g','g','S'};
  109.   while(true){
  110.     auto it = search(data.begin()+ (ptrdiff_t)i, data.end(), sig, sig+4);
  111.     if(it == data.end()) break;
  112.     size_t j = (size_t)(it - data.begin());
  113.     if(j + 27 > data.size()) break;
  114.  
  115.     uint8_t header_type = data[j+5];
  116.     uint64_t granule = 0; for(int k=0;k<8;k++) granule |= (uint64_t)data[j+6+k] << (8*k);
  117.     uint32_t serial = 0; for(int k=0;k<4;k++) serial |= (uint32_t)data[j+14+k] << (8*k);
  118.     uint32_t seq = 0; for(int k=0;k<4;k++) seq |= (uint32_t)data[j+18+k] << (8*k);
  119.     uint8_t nseg = data[j+26];
  120.     if(j + 27 + nseg > data.size()) break;
  121.  
  122.     vector<uint8_t> segtable(data.begin()+j+27, data.begin()+j+27+nseg);
  123.     size_t body_len = 0; for(uint8_t s: segtable) body_len += s;
  124.     size_t body_start = j + 27 + nseg;
  125.     if(body_start + body_len > data.size()) break;
  126.  
  127.     vector<uint8_t> body(data.begin()+body_start, data.begin()+body_start+body_len);
  128.     vector<uint8_t> bytes(data.begin()+j, data.begin()+body_start+body_len);
  129.  
  130.     pages.push_back({header_type, granule, serial, seq, segtable, body, bytes});
  131.     i = body_start + body_len;
  132.   }
  133.   return pages;
  134. }
  135.  
  136. static uint32_t vorbis_sample_rate(const vector<OggPage>& pages){
  137.   vector<uint8_t> cur;
  138.   for(const auto& p: pages){
  139.     size_t off=0;
  140.     for(uint8_t s: p.segtable){
  141.       cur.insert(cur.end(), p.body.begin()+ (ptrdiff_t)off, p.body.begin()+ (ptrdiff_t)(off+s));
  142.       off += s;
  143.       if(s < 255){
  144.         if(cur.size() >= 16 && cur[0]==0x01 && memcmp(cur.data()+1,"vorbis",6)==0){
  145.           uint32_t sr=0;
  146.           sr |= (uint32_t)cur[12];
  147.           sr |= (uint32_t)cur[13]<<8;
  148.           sr |= (uint32_t)cur[14]<<16;
  149.           sr |= (uint32_t)cur[15]<<24;
  150.           return sr;
  151.         }
  152.         return 0;
  153.       }
  154.     }
  155.   }
  156.   return 0;
  157. }
  158.  
  159. static size_t vorbis_header_end_page_index(const vector<OggPage>& pages){
  160.   int pkt_count=0;
  161.   for(size_t idx=0; idx<pages.size(); idx++){
  162.     for(uint8_t s: pages[idx].segtable){
  163.       if(s < 255){
  164.         pkt_count++;
  165.         if(pkt_count >= 3) return idx; // id/comment/setup packets complete
  166.       }
  167.     }
  168.   }
  169.   return pages.empty()?0:pages.size()-1;
  170. }
  171.  
  172. // ------------- OGM (OggDS) header packets for video -------------
  173. static vector<uint8_t> build_vorbis_comment(){
  174.   const string vendor = "dvbang";
  175.   vector<uint8_t> v;
  176.   append_le32(v, (uint32_t)vendor.size());
  177.   v.insert(v.end(), vendor.begin(), vendor.end());
  178.   append_le32(v, 0); // user_comment_list_len
  179.   return v;
  180. }
  181.  
  182. // stream_header packed to 52 bytes (ogmstreams.h style)
  183. static vector<uint8_t> build_ogm_stream_header_video(int width,int height,int fps){
  184.   vector<uint8_t> sh;
  185.   auto put = [&](const void* p, size_t n){
  186.     const uint8_t* b = (const uint8_t*)p;
  187.     sh.insert(sh.end(), b, b+n);
  188.   };
  189.  
  190.   char streamtype[8] = {'v','i','d','e','o',0,0,0};
  191.   char subtype[4] = {'X','V','I','D'}; // MPEG-4 Part 2 commonly tagged XVID in OGM
  192.   int32_t size = 52;
  193.   int64_t time_unit = (int64_t) llround(10000000.0 / (double)fps);
  194.   int64_t samples_per_unit = 1;
  195.   int32_t default_len = 1;
  196.   int32_t buffersize = 0;
  197.   int16_t bits_per_sample = 0;
  198.   int16_t padding = 0;
  199.   int32_t w = width;
  200.   int32_t h = height;
  201.  
  202.   put(streamtype, 8);
  203.   put(subtype, 4);
  204.   put(&size, 4);
  205.   put(&time_unit, 8);
  206.   put(&samples_per_unit, 8);
  207.   put(&default_len, 4);
  208.   put(&buffersize, 4);
  209.   put(&bits_per_sample, 2);
  210.   put(&w, 4);
  211.   put(&h, 4);
  212.   put(&padding, 2);
  213.  
  214.   if(sh.size() != 52) throw runtime_error("internal: stream_header size mismatch");
  215.   return sh;
  216. }
  217.  
  218. static vector<uint8_t> ogm_video_header_packet(int width,int height,int fps){
  219.   auto sh = build_ogm_stream_header_video(width,height,fps);
  220.   vector<uint8_t> pkt; pkt.push_back(0x01);
  221.   pkt.insert(pkt.end(), sh.begin(), sh.end());
  222.   return pkt;
  223. }
  224.  
  225. static vector<uint8_t> ogm_video_comment_packet(){
  226.   auto c = build_vorbis_comment();
  227.   vector<uint8_t> pkt; pkt.push_back(0x03);
  228.   pkt.insert(pkt.end(), c.begin(), c.end());
  229.   return pkt;
  230. }
  231.  
  232. static vector<uint8_t> ogm_video_data_packet(const vector<uint8_t>& frame){
  233.   // lenbytes=2, samples=1, keyframe=1 (good enough for these files)
  234.   const int lb = 2;
  235.   int bit0 = lb & 1;
  236.   int bit1 = (lb & 2) >> 1;
  237.   int bit2 = (lb & 4) >> 2;
  238.  
  239.   uint8_t flags = 0x08; // keyframe
  240.   flags |= (uint8_t)((bit0<<6) | (bit1<<7) | (bit2<<1));
  241.  
  242.   vector<uint8_t> pkt;
  243.   pkt.push_back(flags);
  244.   pkt.push_back(1); pkt.push_back(0); // samples=1 (LE16)
  245.   pkt.insert(pkt.end(), frame.begin(), frame.end());
  246.   return pkt;
  247. }
  248.  
  249. // ------------- MPEG-4 Part 2 ES -> frames (split by VOP startcode 0x000001B6) -------------
  250. static vector<vector<uint8_t>> split_mpeg4_vop_frames(const vector<uint8_t>& es){
  251.   const uint8_t vop_sc[4] = {0x00,0x00,0x01,0xB6};
  252.   vector<size_t> idxs;
  253.   for(size_t i=0;i+4<=es.size(); ){
  254.     auto it = search(es.begin()+ (ptrdiff_t)i, es.end(), vop_sc, vop_sc+4);
  255.     if(it==es.end()) break;
  256.     size_t j=(size_t)(it-es.begin());
  257.     idxs.push_back(j);
  258.     i=j+4;
  259.   }
  260.   if(idxs.empty()) throw runtime_error("No MPEG-4 VOP start codes (0x000001B6) found");
  261.  
  262.   vector<uint8_t> header(es.begin(), es.begin()+ (ptrdiff_t)idxs[0]);
  263.   vector<vector<uint8_t>> frames;
  264.   frames.reserve(idxs.size());
  265.   for(size_t k=0;k<idxs.size();k++){
  266.     size_t start=idxs[k];
  267.     size_t end = (k+1<idxs.size())?idxs[k+1]:es.size();
  268.     frames.emplace_back(es.begin()+ (ptrdiff_t)start, es.begin()+ (ptrdiff_t)end);
  269.   }
  270.   // prepend VOL/headers to first frame
  271.   frames[0].insert(frames[0].begin(), header.begin(), header.end());
  272.   return frames;
  273. }
  274.  
  275. // ------------- DV!! extraction (same as your working Python structure) -------------
  276. static uint32_t rd32le(const vector<uint8_t>& v, size_t off){
  277.   return (uint32_t)v[off] | ((uint32_t)v[off+1]<<8) | ((uint32_t)v[off+2]<<16) | ((uint32_t)v[off+3]<<24);
  278. }
  279.  
  280. struct DVExtract {
  281.   uint32_t width=0, height=0;
  282.   vector<uint8_t> video_es;
  283.   vector<uint8_t> audio_ogg;
  284. };
  285.  
  286. static DVExtract extract_dvbang(const vector<uint8_t>& dv){
  287.   if(dv.size() < 76) throw runtime_error("File too small for DV!! header");
  288.   uint32_t hdr[19];
  289.   for(int i=0;i<19;i++) hdr[i] = rd32le(dv, (size_t)i*4);
  290.   if(hdr[0] != MAGIC_DV) throw runtime_error("Bad magic (expected DV!!)");
  291.   uint32_t width=hdr[4], height=hdr[5];
  292.   uint32_t n=hdr[7];
  293.   uint32_t preroll_len=hdr[9];
  294.  
  295.   size_t table_off=76;
  296.   size_t table_len = (size_t)n * 20;
  297.   if(dv.size() < table_off + table_len) throw runtime_error("Truncated: packet table");
  298.  
  299.   struct Entry{ uint32_t size_flags; uint32_t a; uint32_t b; uint32_t audio_bytes; uint32_t c; };
  300.   vector<Entry> entries; entries.reserve(n);
  301.   for(uint32_t i=0;i<n;i++){
  302.     size_t off = table_off + (size_t)i*20;
  303.     entries.push_back({rd32le(dv,off), rd32le(dv,off+4), rd32le(dv,off+8), rd32le(dv,off+12), rd32le(dv,off+16)});
  304.   }
  305.  
  306.   size_t data_off = table_off + table_len;
  307.   if(dv.size() < data_off + preroll_len) throw runtime_error("Truncated: missing preroll");
  308.  
  309.   vector<uint8_t> preroll(dv.begin()+ (ptrdiff_t)data_off, dv.begin()+ (ptrdiff_t)(data_off+preroll_len));
  310.   size_t pos = data_off + preroll_len;
  311.  
  312.   vector<uint8_t> video;
  313.   vector<uint8_t> audio = preroll;
  314.  
  315.   for(uint32_t i=0;i<n;i++){
  316.     uint32_t size = entries[i].size_flags & 0x7FFFFFFFu;
  317.     uint32_t ab = entries[i].audio_bytes;
  318.     if(pos + size > dv.size()) throw runtime_error("Truncated: packet region");
  319.     const uint8_t* chunk = dv.data() + pos;
  320.     pos += size;
  321.  
  322.     if(ab){
  323.       if(ab > size) throw runtime_error("Corrupt: audio_bytes > packet size");
  324.       uint32_t vlen = size - ab;
  325.       video.insert(video.end(), chunk, chunk + vlen);
  326.       audio.insert(audio.end(), chunk + vlen, chunk + size);
  327.     } else {
  328.       video.insert(video.end(), chunk, chunk + size);
  329.     }
  330.   }
  331.  
  332.   return DVExtract{width,height,move(video),move(audio)};
  333. }
  334.  
  335. static void write_all(ofstream& f, const vector<uint8_t>& b){
  336.   f.write((const char*)b.data(), (streamsize)b.size());
  337. }
  338.  
  339. static int convert_file(const Path& inpath, const Path& outpath, int fps, bool validate){
  340.   ifstream fi(inpath, ios::binary);
  341.   if(!fi){ eprint("Cannot open input file"); return 2; }
  342.   vector<uint8_t> dv((istreambuf_iterator<char>(fi)), {});
  343.   fi.close();
  344.  
  345.   DVExtract ex;
  346.   try { ex = extract_dvbang(dv); }
  347.   catch(const exception& e){ eprint(string("Invalid input: ") + e.what()); return 2; }
  348.  
  349.   auto audio_pages = parse_ogg_pages(ex.audio_ogg);
  350.   if(audio_pages.empty()){ eprint("Invalid audio stream: no Ogg pages"); return 2; }
  351.  
  352.   uint32_t audio_serial = audio_pages[0].serial;
  353.   uint32_t video_serial = 0xC0DEC0DEu; // deterministic
  354.   if(video_serial == audio_serial) video_serial ^= 0xFFFFFFFFu;
  355.  
  356.   uint32_t sr = vorbis_sample_rate(audio_pages);
  357.   if(sr == 0){ eprint("WARNING: could not detect Vorbis sample rate, assuming 44100"); sr = 44100; }
  358.  
  359.   size_t hdr_end = vorbis_header_end_page_index(audio_pages);
  360.  
  361.   vector<vector<uint8_t>> frames;
  362.   try { frames = split_mpeg4_vop_frames(ex.video_es); }
  363.   catch(const exception& e){ eprint(string("Video parse error: ") + e.what()); return 2; }
  364.  
  365.   auto v_header = ogm_video_header_packet((int)ex.width,(int)ex.height,fps);
  366.   auto v_comment = ogm_video_comment_packet();
  367.  
  368.   ofstream fo(outpath, ios::binary);
  369.   if(!fo){ eprint("Cannot open output file for writing"); return 2; }
  370.  
  371.   // BOS first: audio BOS (unchanged), then video BOS (our header), then video comment
  372.   write_all(fo, audio_pages[0].bytes);
  373.   write_all(fo, build_ogg_page({v_header}, video_serial, 0, 0, 0x02)); // BOS
  374.   uint32_t vseq = 1;
  375.   write_all(fo, build_ogg_page({v_comment}, video_serial, vseq++, 0, 0x00));
  376.  
  377.   // rest of Vorbis header pages (unchanged)
  378.   for(size_t i=1; i<=hdr_end && i<audio_pages.size(); i++) write_all(fo, audio_pages[i].bytes);
  379.  
  380.   // interleave: emit video frames up to each audio page timestamp, then copy the audio page
  381.   size_t v_i=0;
  382.   for(size_t ai=hdr_end+1; ai<audio_pages.size(); ai++){
  383.     const auto& ap = audio_pages[ai];
  384.     double at = (double)ap.granule / (double)sr;
  385.  
  386.     while(v_i < frames.size() && ((double)(v_i+1) / (double)fps) <= at + (0.5 / (double)fps)){
  387.       bool last = (v_i + 1 == frames.size());
  388.       uint8_t ht = last ? 0x04 : 0x00; // EOS on last page
  389.       auto pkt = ogm_video_data_packet(frames[v_i]);
  390.       uint64_t gran = (uint64_t)(v_i + 1); // granulepos = frames decoded
  391.       write_all(fo, build_ogg_page({pkt}, video_serial, vseq++, gran, ht));
  392.       v_i++;
  393.     }
  394.     write_all(fo, ap.bytes); // audio page byte-identical
  395.   }
  396.  
  397.   // flush remaining video
  398.   while(v_i < frames.size()){
  399.     bool last = (v_i + 1 == frames.size());
  400.     uint8_t ht = last ? 0x04 : 0x00;
  401.     auto pkt = ogm_video_data_packet(frames[v_i]);
  402.     uint64_t gran = (uint64_t)(v_i + 1);
  403.     write_all(fo, build_ogg_page({pkt}, video_serial, vseq++, gran, ht));
  404.     v_i++;
  405.   }
  406.  
  407.   fo.close();
  408.  
  409.   cout << "ogv: " << outpath.string() << "\n";
  410.   cout << "video: MPEG-4 Part 2 (" << ex.width << "x" << ex.height << ", frames=" << frames.size() << ", fps=" << fps << ")\n";
  411.   cout << "audio: Vorbis (sr=" << sr << ", pages=" << audio_pages.size() << ")\n";
  412.  
  413.   if(validate){
  414.     string cmd = "ffmpeg -hide_banner -loglevel error -i \"" + outpath.string() + "\" -f null -";
  415.     int rc = system(cmd.c_str());
  416.     if(rc != 0){
  417.       eprint("WARNING: ffmpeg validation failed (ffmpeg missing or decode errors). Output file still written.");
  418.       return 1;
  419.     }
  420.   }
  421.  
  422.   return 0;
  423. }
  424.  
  425. int main(int argc, char** argv){
  426.   if(argc < 2){ usage(argv[0]); return 2; }
  427.  
  428.   bool validate = false;
  429.   vector<string> pos;
  430.  
  431.   for(int i=1;i<argc;i++){
  432.     string a = argv[i];
  433.     if(a=="-h" || a=="--help") { usage(argv[0]); return 0; }
  434.     if(a=="--validate") { validate = true; continue; }
  435.     if(!a.empty() && a[0]=='-') { eprint("Unknown option: " + a); usage(argv[0]); return 2; }
  436.     pos.push_back(a);
  437.   }
  438.  
  439.   if(pos.empty()) { usage(argv[0]); return 2; }
  440.  
  441.   Path in = pos[0];
  442.   Path out;
  443.   if(pos.size() >= 2) out = pos[1];
  444.   else { out = in; out.replace_extension(".ogv"); }
  445.  
  446.   const int fps = 25; // for your DV!! files
  447.   try {
  448.     return convert_file(in, out, fps, validate);
  449.   } catch(const exception& e){
  450.     eprint(string("ERROR: ") + e.what());
  451.     return 2;
  452.   }
  453. }
  454.  
Advertisement
Add Comment
Please, Sign In to add comment