Created
December 4, 2019 21:49
-
-
Save dwbuiten/6757b9bd7682b2da6087af761ff9732b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifdef _WIN32 | |
#define fseeko _fseeki64 | |
#define ftello _fseeki64 | |
#define off_t __int64 | |
#else | |
#define _FILE_OFFSET_BITS 64 | |
#define _LARGEFILE_SOURCE | |
#endif | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <libavutil/common.h> | |
#include <libavutil/intreadwrite.h> | |
/* | |
* WARNING: This is nothing even close to a real ISOBMFF parser! | |
* It does not actually do hierarchical box parsing, and also | |
* assumes boxes are well-ordered, and that the first trak box is | |
* the video. It also assumes the file is >2^32 bytes. | |
* | |
* Am I ashamed? Maybe. | |
*/ | |
/* Finds a given box, and descends into it. */ | |
int find_box(FILE *f, uint32_t box_name) | |
{ | |
while (!feof(f)) { | |
uint8_t buf[8]; | |
uint32_t box; | |
uint64_t size; | |
off_t adj = 8; | |
int ret; | |
ret = fread(&buf[0], 1, 8, f); | |
if (ret != 8) { | |
printf("failed to read.\n"); | |
return -1; | |
} | |
box = AV_RB32(&buf[4]); | |
size = AV_RB32(&buf[0]); | |
if (size == 0) { | |
printf("too lazy to support this\n"); | |
return -1; | |
} else if (size == 1) { | |
/* Extended box size. */ | |
ret = fread(&buf[0], 1, 8, f); | |
if (ret != 8) { | |
printf("failed to read.\n"); | |
return -1; | |
} | |
size = AV_RB64(&buf[0]); | |
adj += 8; | |
} | |
if (size < 8) { | |
printf("invalid box\n"); | |
return -1; | |
} else if (size == 8) { | |
continue; | |
} | |
if (box == box_name) | |
return 0; | |
ret = fseeko(f, ((off_t) size) - adj, SEEK_CUR); | |
if (ret < 0) { | |
printf("failed to seek %"PRIu64" bytes forward\n", size); | |
return -1; | |
} | |
} | |
printf("end of file.\n"); | |
return -1; | |
} | |
/* Locates the first trak's stsz and co64 boxes. */ | |
int find_stsz_co64(FILE *f, off_t *stsz_pos, off_t *co64_pos) | |
{ | |
const uint32_t hierarchy[5] = { | |
MKBETAG('m','o','o','v'), | |
MKBETAG('t','r','a','k'), | |
MKBETAG('m','d','i','a'), | |
MKBETAG('m','i','n','f'), | |
MKBETAG('s','t','b','l') | |
}; | |
int i; | |
int ret; | |
off_t stbl_pos; | |
for (i = 0; i < 5; i++) { | |
ret = find_box(f, hierarchy[i]); | |
if (ret < 0) { | |
printf("failed to find box: %"PRIx32"\n"); | |
return -1; | |
} | |
} | |
stbl_pos = ftello(f); | |
ret = find_box(f, MKBETAG('s','t','s','z')); | |
if (ret < 0) { | |
printf("failed to find the stsz box"); | |
return -1; | |
} | |
*stsz_pos = ftello(f); | |
ret = fseeko(f, stbl_pos, SEEK_SET); | |
if (ret < 0) { | |
printf("failed to seek back to stbl offset\n"); | |
return -1; | |
} | |
ret = find_box(f, MKBETAG('c','o','6','4')); | |
if (ret < 0) { | |
printf("failed to find the co64 box"); | |
return -1; | |
} | |
*co64_pos = ftello(f); | |
return 0; | |
} | |
int patch_co64(FILE *f, off_t pos, uint64_t *values, size_t entries) { | |
size_t i; | |
int ret = fseeko(f, pos + 8, SEEK_SET); /* skip version + flags + entries */ | |
if (ret < 0) { | |
printf("failed to seek\n"); | |
return -1; | |
} | |
for (i = 0; i < entries; i++) { | |
uint8_t buf[8]; | |
AV_WB64(&buf[0], values[i]); | |
fwrite(&buf[0], 1, 8, f); | |
} | |
return 0; | |
} | |
uint64_t *read_co64(FILE *f, off_t pos, size_t *entries) | |
{ | |
uint8_t buf[8]; | |
uint64_t *vals; | |
size_t i; | |
int ret = fseeko(f, pos + 4, SEEK_SET); /* skip version + flags */ | |
if (ret < 0) { | |
printf("failed to seek\n"); | |
return NULL; | |
} | |
ret = fread(&buf[0], 1, 4, f); | |
if (ret != 4) { | |
printf("failed to read\n"); | |
return NULL; | |
} | |
*entries = AV_RB32(&buf[0]); | |
if (*entries == 0) { | |
printf("invalid co64 box\n"); | |
return NULL; | |
} | |
vals = malloc(*entries * sizeof(*vals)); | |
if (vals == NULL) { | |
printf("alloc failure\n"); | |
return NULL; | |
} | |
for (i = 0; i < *entries; i++) { | |
ret = fread(&buf[0], 1, 8, f); | |
if (ret != 8) { | |
free(vals); | |
printf("failed to read entry\n"); | |
return NULL; | |
} | |
vals[i] = AV_RB64(&buf[0]); | |
} | |
return vals; | |
} | |
int patch_stsz(FILE *f, off_t pos, uint32_t *values, uint64_t *covalues, uint64_t *new_offsets, size_t entries) { | |
size_t i; | |
int ret = fseeko(f, pos + 12, SEEK_SET); /* skip version + flags + uniform + entries */ | |
if (ret < 0) { | |
printf("failed to seek\n"); | |
return -1; | |
} | |
for (i = 0; i < entries - 1; i++) { | |
/* | |
* This is in no way correct, since the offsets have moved around a bit, but "good enough". | |
* The proper way would be to take audio packet placement into account manually. :effort: | |
*/ | |
uint32_t adj = ((uint32_t)(covalues[i+1] - covalues[i])) - values[i]; | |
uint32_t new_val = ((uint32_t)(new_offsets[i+1] - new_offsets[i])) - adj; | |
uint8_t buf[4]; | |
AV_WB32(&buf[0], new_val); | |
fwrite(&buf[0], 1, 4, f); | |
} | |
/* TODO: Last sample size */ | |
return 0; | |
} | |
uint32_t *read_stsz(FILE *f, off_t pos, size_t *entries) | |
{ | |
uint8_t buf[8]; | |
uint32_t *vals; | |
size_t i; | |
int ret = fseeko(f, pos + 4, SEEK_SET); /* skip version + flags */ | |
if (ret < 0) { | |
printf("failed to seek\n"); | |
return NULL; | |
} | |
ret = fread(&buf[0], 1, 8, f); | |
if (ret != 8) { | |
printf("failed to read\n"); | |
return NULL; | |
} | |
if (AV_RB32(&buf[0]) != 0) { | |
printf("samples should not be uniform\n"); | |
return NULL; | |
} | |
*entries = AV_RB32(&buf[4]); | |
if (*entries == 0) { | |
printf("invalid stsz box\n"); | |
return NULL; | |
} | |
vals = malloc(*entries * sizeof(*vals)); | |
if (vals == NULL) { | |
printf("alloc failure\n"); | |
return NULL; | |
} | |
for (i = 0; i < *entries; i++) { | |
ret = fread(&buf[0], 1, 4, f); | |
if (ret != 4) { | |
free(vals); | |
printf("failed to read entry\n"); | |
return NULL; | |
} | |
vals[i] = AV_RB32(&buf[0]); | |
} | |
return vals; | |
} | |
static inline void bshift(uint8_t *buf, uint8_t val) { | |
buf[0] = buf[1]; | |
buf[1] = buf[2]; | |
buf[2] = buf[3]; | |
buf[3] = buf[4]; | |
buf[4] = buf[5]; | |
buf[5] = buf[6]; | |
buf[6] = buf[7]; | |
buf[7] = val; | |
} | |
uint64_t *index_frames(FILE *f, size_t max_size) { | |
const uint8_t header[8] = {0, 1, 0, 9, 0, 2, 0, 3}; | |
uint8_t buf[8]; | |
int count = 0; | |
uint64_t *vals; | |
int ret; | |
ret = fseeko(f, 0, SEEK_SET); | |
if (ret < 0) { | |
printf("failed to seek to start\n"); | |
return NULL; | |
} | |
ret = find_box(f, MKBETAG('m','d','a','t')); | |
if (ret < 0) { | |
printf("couldn't find mdat\n"); | |
return NULL; | |
} | |
vals = malloc(max_size * sizeof(*vals)); | |
if (vals == NULL) { | |
printf("alloc failure\n"); | |
return NULL; | |
} | |
/* Initial fill. */ | |
fread(&buf[0], 1, 8, f); | |
/* World's slowest and worst search. Byte. By. Byte. Not even by block. */ | |
while (!feof(f) && count < max_size) { | |
uint8_t new_val; | |
if (!memcmp(&header[0], &buf[0], 8)) { | |
off_t pos = ftello(f) - 8; | |
vals[count] = pos; | |
printf("frame found at pos %ld (count=%d)\n", pos, count); | |
count++; | |
} | |
fread(&new_val, 1, 1, f); | |
bshift(&buf[0], new_val); | |
} | |
if (count < max_size) { | |
free(vals); | |
printf("woops indexing didn't work...\n"); | |
return NULL; | |
} | |
return vals; | |
} | |
int main(int argc, char *argv[]) | |
{ | |
FILE *in, *out; | |
int ret; | |
off_t stsz_pos, co64_pos; | |
size_t co64_entries, stsz_entries; | |
uint32_t *stsz_vals; | |
uint64_t *co64_vals, *new_offsets; | |
if (argc < 3) { | |
printf("usage: hack <input file> <output file>\n"); | |
return 0; | |
} | |
in = fopen(argv[1], "rb"); | |
if (in == NULL) { | |
printf("failed to open input file.\n"); | |
return 1; | |
} | |
ret = find_stsz_co64(in, &stsz_pos, &co64_pos); | |
if (ret < 0) { | |
printf("failed to find stsz and co64 positions\n"); | |
fclose(in); | |
return 1; | |
} | |
printf("stsz: %ld / co64: %ld\n", stsz_pos, co64_pos); | |
co64_vals = read_co64(in, co64_pos, &co64_entries); | |
if (co64_vals == NULL) { | |
printf("failed to read co64 box.\n"); | |
fclose(in); | |
return 1; | |
} | |
printf("read %zu co64 offsets\n", co64_entries); | |
stsz_vals = read_stsz(in, stsz_pos, &stsz_entries); | |
if (stsz_vals == NULL) { | |
printf("failed to read stsz box.\n"); | |
free(co64_vals); | |
fclose(in); | |
return 1; | |
} | |
printf("read %zu stsz sizes\n", stsz_entries); | |
if (stsz_entries != co64_entries) { | |
printf("stsz and co64 have differing numbers of entries!\n"); | |
free(stsz_vals); | |
free(co64_vals); | |
fclose(in); | |
return 1; | |
} | |
new_offsets = index_frames(in, co64_entries); | |
if (new_offsets == NULL) { | |
printf("failed to index file\n"); | |
free(stsz_vals); | |
free(co64_vals); | |
fclose(in); | |
} | |
fclose(in); | |
out = fopen(argv[2], "r+b"); | |
if (out == NULL) { | |
printf("could not open output file\n"); | |
return 1; | |
} | |
ret = patch_co64(out, co64_pos, new_offsets, co64_entries); | |
if (ret < 0) { | |
free(stsz_vals); | |
free(new_offsets); | |
free(co64_vals); | |
fclose(out); | |
printf("failed to patch co64 box\n"); | |
return 1; | |
} | |
ret = patch_stsz(out, stsz_pos, stsz_vals, co64_vals, new_offsets, stsz_entries); | |
if (ret < 0) { | |
free(stsz_vals); | |
free(new_offsets); | |
free(co64_vals); | |
fclose(out); | |
printf("failed to patch stsz box\n"); | |
return 1; | |
} | |
free(stsz_vals); | |
free(new_offsets); | |
free(co64_vals); | |
fclose(out); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment