Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include <cstdint>
- #include <cstring>
- #include <cmath>
- #include <fstream>
- #include <iostream>
- #include <stdexcept>
- #include <string>
- #include <vector>
- #include <filesystem>
- #include <algorithm>
- using namespace std;
- using Path = std::filesystem::path;
- static const uint32_t MAGIC_DV = 0x21215644u; // "DV!!" little-endian
- static void eprint(const string& s){ cerr << s << "\n"; }
- static void usage(const char* argv0){
- cerr << "Usage:\n " << argv0 << " <input.dv> [output.ogv] [--validate]\n\n"
- << "Converts DV!! (non-standard) .dv into Ogg/OGM .ogv without ffmpeg muxing.\n"
- << "Options:\n"
- << " --validate Run ffmpeg decode test on output (disabled by default)\n"
- << " -h, --help Show this help\n";
- }
- // ---------------- Ogg CRC ----------------
- static vector<uint32_t> make_crc_table(){
- const uint32_t poly = 0x04C11DB7u;
- vector<uint32_t> t(256);
- for(uint32_t i=0;i<256;i++){
- uint32_t r = i << 24;
- for(int j=0;j<8;j++) r = (r & 0x80000000u) ? ((r<<1) ^ poly) : (r<<1);
- t[i]=r;
- }
- return t;
- }
- static const vector<uint32_t> CRC_TABLE = make_crc_table();
- static uint32_t ogg_crc(const vector<uint8_t>& page){
- uint32_t crc=0;
- for(uint8_t b: page){
- crc = (crc<<8) ^ CRC_TABLE[((crc>>24)&0xFFu) ^ b];
- }
- return crc;
- }
- static void append_le32(vector<uint8_t>& v, uint32_t x){
- v.push_back((uint8_t)(x & 0xFF));
- v.push_back((uint8_t)((x>>8)&0xFF));
- v.push_back((uint8_t)((x>>16)&0xFF));
- v.push_back((uint8_t)((x>>24)&0xFF));
- }
- static void append_le64(vector<uint8_t>& v, uint64_t x){
- for(int i=0;i<8;i++) v.push_back((uint8_t)((x>>(8*i))&0xFF));
- }
- static vector<uint8_t> build_ogg_page(const vector<vector<uint8_t>>& packets,
- uint32_t serial, uint32_t seq,
- uint64_t granule, uint8_t header_type){
- vector<uint8_t> segs;
- vector<uint8_t> body;
- for(const auto& pkt: packets){
- body.insert(body.end(), pkt.begin(), pkt.end());
- size_t n = pkt.size();
- while(n >= 255){ segs.push_back(255); n -= 255; }
- segs.push_back((uint8_t)n);
- }
- if(segs.size() > 255) throw runtime_error("Too many segments in one page");
- vector<uint8_t> header;
- header.insert(header.end(), {'O','g','g','S'});
- header.push_back(0); // version
- header.push_back(header_type);
- append_le64(header, granule);
- append_le32(header, serial);
- append_le32(header, seq);
- append_le32(header, 0); // CRC placeholder
- header.push_back((uint8_t)segs.size());
- header.insert(header.end(), segs.begin(), segs.end());
- vector<uint8_t> page = header;
- page.insert(page.end(), body.begin(), body.end());
- uint32_t crc = ogg_crc(page);
- page[22] = (uint8_t)(crc & 0xFF);
- page[23] = (uint8_t)((crc>>8)&0xFF);
- page[24] = (uint8_t)((crc>>16)&0xFF);
- page[25] = (uint8_t)((crc>>24)&0xFF);
- return page;
- }
- // ------------- Ogg page parsing (audio pages copied as-is) -------------
- struct OggPage {
- uint8_t header_type;
- uint64_t granule;
- uint32_t serial;
- uint32_t seq;
- vector<uint8_t> segtable;
- vector<uint8_t> body;
- vector<uint8_t> bytes; // full original page bytes
- };
- static vector<OggPage> parse_ogg_pages(const vector<uint8_t>& data){
- vector<OggPage> pages;
- size_t i=0;
- const uint8_t sig[4] = {'O','g','g','S'};
- while(true){
- auto it = search(data.begin()+ (ptrdiff_t)i, data.end(), sig, sig+4);
- if(it == data.end()) break;
- size_t j = (size_t)(it - data.begin());
- if(j + 27 > data.size()) break;
- uint8_t header_type = data[j+5];
- uint64_t granule = 0; for(int k=0;k<8;k++) granule |= (uint64_t)data[j+6+k] << (8*k);
- uint32_t serial = 0; for(int k=0;k<4;k++) serial |= (uint32_t)data[j+14+k] << (8*k);
- uint32_t seq = 0; for(int k=0;k<4;k++) seq |= (uint32_t)data[j+18+k] << (8*k);
- uint8_t nseg = data[j+26];
- if(j + 27 + nseg > data.size()) break;
- vector<uint8_t> segtable(data.begin()+j+27, data.begin()+j+27+nseg);
- size_t body_len = 0; for(uint8_t s: segtable) body_len += s;
- size_t body_start = j + 27 + nseg;
- if(body_start + body_len > data.size()) break;
- vector<uint8_t> body(data.begin()+body_start, data.begin()+body_start+body_len);
- vector<uint8_t> bytes(data.begin()+j, data.begin()+body_start+body_len);
- pages.push_back({header_type, granule, serial, seq, segtable, body, bytes});
- i = body_start + body_len;
- }
- return pages;
- }
- static uint32_t vorbis_sample_rate(const vector<OggPage>& pages){
- vector<uint8_t> cur;
- for(const auto& p: pages){
- size_t off=0;
- for(uint8_t s: p.segtable){
- cur.insert(cur.end(), p.body.begin()+ (ptrdiff_t)off, p.body.begin()+ (ptrdiff_t)(off+s));
- off += s;
- if(s < 255){
- if(cur.size() >= 16 && cur[0]==0x01 && memcmp(cur.data()+1,"vorbis",6)==0){
- uint32_t sr=0;
- sr |= (uint32_t)cur[12];
- sr |= (uint32_t)cur[13]<<8;
- sr |= (uint32_t)cur[14]<<16;
- sr |= (uint32_t)cur[15]<<24;
- return sr;
- }
- return 0;
- }
- }
- }
- return 0;
- }
- static size_t vorbis_header_end_page_index(const vector<OggPage>& pages){
- int pkt_count=0;
- for(size_t idx=0; idx<pages.size(); idx++){
- for(uint8_t s: pages[idx].segtable){
- if(s < 255){
- pkt_count++;
- if(pkt_count >= 3) return idx; // id/comment/setup packets complete
- }
- }
- }
- return pages.empty()?0:pages.size()-1;
- }
- // ------------- OGM (OggDS) header packets for video -------------
- static vector<uint8_t> build_vorbis_comment(){
- const string vendor = "dvbang";
- vector<uint8_t> v;
- append_le32(v, (uint32_t)vendor.size());
- v.insert(v.end(), vendor.begin(), vendor.end());
- append_le32(v, 0); // user_comment_list_len
- return v;
- }
- // stream_header packed to 52 bytes (ogmstreams.h style)
- static vector<uint8_t> build_ogm_stream_header_video(int width,int height,int fps){
- vector<uint8_t> sh;
- auto put = [&](const void* p, size_t n){
- const uint8_t* b = (const uint8_t*)p;
- sh.insert(sh.end(), b, b+n);
- };
- char streamtype[8] = {'v','i','d','e','o',0,0,0};
- char subtype[4] = {'X','V','I','D'}; // MPEG-4 Part 2 commonly tagged XVID in OGM
- int32_t size = 52;
- int64_t time_unit = (int64_t) llround(10000000.0 / (double)fps);
- int64_t samples_per_unit = 1;
- int32_t default_len = 1;
- int32_t buffersize = 0;
- int16_t bits_per_sample = 0;
- int16_t padding = 0;
- int32_t w = width;
- int32_t h = height;
- put(streamtype, 8);
- put(subtype, 4);
- put(&size, 4);
- put(&time_unit, 8);
- put(&samples_per_unit, 8);
- put(&default_len, 4);
- put(&buffersize, 4);
- put(&bits_per_sample, 2);
- put(&w, 4);
- put(&h, 4);
- put(&padding, 2);
- if(sh.size() != 52) throw runtime_error("internal: stream_header size mismatch");
- return sh;
- }
- static vector<uint8_t> ogm_video_header_packet(int width,int height,int fps){
- auto sh = build_ogm_stream_header_video(width,height,fps);
- vector<uint8_t> pkt; pkt.push_back(0x01);
- pkt.insert(pkt.end(), sh.begin(), sh.end());
- return pkt;
- }
- static vector<uint8_t> ogm_video_comment_packet(){
- auto c = build_vorbis_comment();
- vector<uint8_t> pkt; pkt.push_back(0x03);
- pkt.insert(pkt.end(), c.begin(), c.end());
- return pkt;
- }
- static vector<uint8_t> ogm_video_data_packet(const vector<uint8_t>& frame){
- // lenbytes=2, samples=1, keyframe=1 (good enough for these files)
- const int lb = 2;
- int bit0 = lb & 1;
- int bit1 = (lb & 2) >> 1;
- int bit2 = (lb & 4) >> 2;
- uint8_t flags = 0x08; // keyframe
- flags |= (uint8_t)((bit0<<6) | (bit1<<7) | (bit2<<1));
- vector<uint8_t> pkt;
- pkt.push_back(flags);
- pkt.push_back(1); pkt.push_back(0); // samples=1 (LE16)
- pkt.insert(pkt.end(), frame.begin(), frame.end());
- return pkt;
- }
- // ------------- MPEG-4 Part 2 ES -> frames (split by VOP startcode 0x000001B6) -------------
- static vector<vector<uint8_t>> split_mpeg4_vop_frames(const vector<uint8_t>& es){
- const uint8_t vop_sc[4] = {0x00,0x00,0x01,0xB6};
- vector<size_t> idxs;
- for(size_t i=0;i+4<=es.size(); ){
- auto it = search(es.begin()+ (ptrdiff_t)i, es.end(), vop_sc, vop_sc+4);
- if(it==es.end()) break;
- size_t j=(size_t)(it-es.begin());
- idxs.push_back(j);
- i=j+4;
- }
- if(idxs.empty()) throw runtime_error("No MPEG-4 VOP start codes (0x000001B6) found");
- vector<uint8_t> header(es.begin(), es.begin()+ (ptrdiff_t)idxs[0]);
- vector<vector<uint8_t>> frames;
- frames.reserve(idxs.size());
- for(size_t k=0;k<idxs.size();k++){
- size_t start=idxs[k];
- size_t end = (k+1<idxs.size())?idxs[k+1]:es.size();
- frames.emplace_back(es.begin()+ (ptrdiff_t)start, es.begin()+ (ptrdiff_t)end);
- }
- // prepend VOL/headers to first frame
- frames[0].insert(frames[0].begin(), header.begin(), header.end());
- return frames;
- }
- // ------------- DV!! extraction (same as your working Python structure) -------------
- static uint32_t rd32le(const vector<uint8_t>& v, size_t off){
- return (uint32_t)v[off] | ((uint32_t)v[off+1]<<8) | ((uint32_t)v[off+2]<<16) | ((uint32_t)v[off+3]<<24);
- }
- struct DVExtract {
- uint32_t width=0, height=0;
- vector<uint8_t> video_es;
- vector<uint8_t> audio_ogg;
- };
- static DVExtract extract_dvbang(const vector<uint8_t>& dv){
- if(dv.size() < 76) throw runtime_error("File too small for DV!! header");
- uint32_t hdr[19];
- for(int i=0;i<19;i++) hdr[i] = rd32le(dv, (size_t)i*4);
- if(hdr[0] != MAGIC_DV) throw runtime_error("Bad magic (expected DV!!)");
- uint32_t width=hdr[4], height=hdr[5];
- uint32_t n=hdr[7];
- uint32_t preroll_len=hdr[9];
- size_t table_off=76;
- size_t table_len = (size_t)n * 20;
- if(dv.size() < table_off + table_len) throw runtime_error("Truncated: packet table");
- struct Entry{ uint32_t size_flags; uint32_t a; uint32_t b; uint32_t audio_bytes; uint32_t c; };
- vector<Entry> entries; entries.reserve(n);
- for(uint32_t i=0;i<n;i++){
- size_t off = table_off + (size_t)i*20;
- entries.push_back({rd32le(dv,off), rd32le(dv,off+4), rd32le(dv,off+8), rd32le(dv,off+12), rd32le(dv,off+16)});
- }
- size_t data_off = table_off + table_len;
- if(dv.size() < data_off + preroll_len) throw runtime_error("Truncated: missing preroll");
- vector<uint8_t> preroll(dv.begin()+ (ptrdiff_t)data_off, dv.begin()+ (ptrdiff_t)(data_off+preroll_len));
- size_t pos = data_off + preroll_len;
- vector<uint8_t> video;
- vector<uint8_t> audio = preroll;
- for(uint32_t i=0;i<n;i++){
- uint32_t size = entries[i].size_flags & 0x7FFFFFFFu;
- uint32_t ab = entries[i].audio_bytes;
- if(pos + size > dv.size()) throw runtime_error("Truncated: packet region");
- const uint8_t* chunk = dv.data() + pos;
- pos += size;
- if(ab){
- if(ab > size) throw runtime_error("Corrupt: audio_bytes > packet size");
- uint32_t vlen = size - ab;
- video.insert(video.end(), chunk, chunk + vlen);
- audio.insert(audio.end(), chunk + vlen, chunk + size);
- } else {
- video.insert(video.end(), chunk, chunk + size);
- }
- }
- return DVExtract{width,height,move(video),move(audio)};
- }
- static void write_all(ofstream& f, const vector<uint8_t>& b){
- f.write((const char*)b.data(), (streamsize)b.size());
- }
- static int convert_file(const Path& inpath, const Path& outpath, int fps, bool validate){
- ifstream fi(inpath, ios::binary);
- if(!fi){ eprint("Cannot open input file"); return 2; }
- vector<uint8_t> dv((istreambuf_iterator<char>(fi)), {});
- fi.close();
- DVExtract ex;
- try { ex = extract_dvbang(dv); }
- catch(const exception& e){ eprint(string("Invalid input: ") + e.what()); return 2; }
- auto audio_pages = parse_ogg_pages(ex.audio_ogg);
- if(audio_pages.empty()){ eprint("Invalid audio stream: no Ogg pages"); return 2; }
- uint32_t audio_serial = audio_pages[0].serial;
- uint32_t video_serial = 0xC0DEC0DEu; // deterministic
- if(video_serial == audio_serial) video_serial ^= 0xFFFFFFFFu;
- uint32_t sr = vorbis_sample_rate(audio_pages);
- if(sr == 0){ eprint("WARNING: could not detect Vorbis sample rate, assuming 44100"); sr = 44100; }
- size_t hdr_end = vorbis_header_end_page_index(audio_pages);
- vector<vector<uint8_t>> frames;
- try { frames = split_mpeg4_vop_frames(ex.video_es); }
- catch(const exception& e){ eprint(string("Video parse error: ") + e.what()); return 2; }
- auto v_header = ogm_video_header_packet((int)ex.width,(int)ex.height,fps);
- auto v_comment = ogm_video_comment_packet();
- ofstream fo(outpath, ios::binary);
- if(!fo){ eprint("Cannot open output file for writing"); return 2; }
- // BOS first: audio BOS (unchanged), then video BOS (our header), then video comment
- write_all(fo, audio_pages[0].bytes);
- write_all(fo, build_ogg_page({v_header}, video_serial, 0, 0, 0x02)); // BOS
- uint32_t vseq = 1;
- write_all(fo, build_ogg_page({v_comment}, video_serial, vseq++, 0, 0x00));
- // rest of Vorbis header pages (unchanged)
- for(size_t i=1; i<=hdr_end && i<audio_pages.size(); i++) write_all(fo, audio_pages[i].bytes);
- // interleave: emit video frames up to each audio page timestamp, then copy the audio page
- size_t v_i=0;
- for(size_t ai=hdr_end+1; ai<audio_pages.size(); ai++){
- const auto& ap = audio_pages[ai];
- double at = (double)ap.granule / (double)sr;
- while(v_i < frames.size() && ((double)(v_i+1) / (double)fps) <= at + (0.5 / (double)fps)){
- bool last = (v_i + 1 == frames.size());
- uint8_t ht = last ? 0x04 : 0x00; // EOS on last page
- auto pkt = ogm_video_data_packet(frames[v_i]);
- uint64_t gran = (uint64_t)(v_i + 1); // granulepos = frames decoded
- write_all(fo, build_ogg_page({pkt}, video_serial, vseq++, gran, ht));
- v_i++;
- }
- write_all(fo, ap.bytes); // audio page byte-identical
- }
- // flush remaining video
- while(v_i < frames.size()){
- bool last = (v_i + 1 == frames.size());
- uint8_t ht = last ? 0x04 : 0x00;
- auto pkt = ogm_video_data_packet(frames[v_i]);
- uint64_t gran = (uint64_t)(v_i + 1);
- write_all(fo, build_ogg_page({pkt}, video_serial, vseq++, gran, ht));
- v_i++;
- }
- fo.close();
- cout << "ogv: " << outpath.string() << "\n";
- cout << "video: MPEG-4 Part 2 (" << ex.width << "x" << ex.height << ", frames=" << frames.size() << ", fps=" << fps << ")\n";
- cout << "audio: Vorbis (sr=" << sr << ", pages=" << audio_pages.size() << ")\n";
- if(validate){
- string cmd = "ffmpeg -hide_banner -loglevel error -i \"" + outpath.string() + "\" -f null -";
- int rc = system(cmd.c_str());
- if(rc != 0){
- eprint("WARNING: ffmpeg validation failed (ffmpeg missing or decode errors). Output file still written.");
- return 1;
- }
- }
- return 0;
- }
- int main(int argc, char** argv){
- if(argc < 2){ usage(argv[0]); return 2; }
- bool validate = false;
- vector<string> pos;
- for(int i=1;i<argc;i++){
- string a = argv[i];
- if(a=="-h" || a=="--help") { usage(argv[0]); return 0; }
- if(a=="--validate") { validate = true; continue; }
- if(!a.empty() && a[0]=='-') { eprint("Unknown option: " + a); usage(argv[0]); return 2; }
- pos.push_back(a);
- }
- if(pos.empty()) { usage(argv[0]); return 2; }
- Path in = pos[0];
- Path out;
- if(pos.size() >= 2) out = pos[1];
- else { out = in; out.replace_extension(".ogv"); }
- const int fps = 25; // for your DV!! files
- try {
- return convert_file(in, out, fps, validate);
- } catch(const exception& e){
- eprint(string("ERROR: ") + e.what());
- return 2;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment