ref: 69533e50211716795286117c83686d051d776ba5
dir: /src/opusinfo.c/
/* Opusinfo * * A tool to describe opus file contents and metadata. * * This is a fork of ogginfo from the vorbis-tools package * which has been cut down to only have opus support. * * Ogginfo is * Copyright 2002-2005 Michael Smith <msmith@xiph.org> * Licensed under the GNU GPL, distributed with this program. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <stdarg.h> #include <getopt.h> #include <math.h> #include <ogg/ogg.h> /*No NLS support for now*/ #define _(X) (X) #include "opusinfo.h" #include "opus_header.h" #include "info_opus.h" #include "picture.h" #if defined WIN32 || defined _WIN32 # include "unicode_support.h" #else # define fopen_utf8(_x,_y) fopen((_x),(_y)) # define argc_utf8 argc # define argv_utf8 argv #endif #define CHUNK 4500 static int printlots = 0; static int printinfo = 1; static int printwarn = 1; static int verbose = 1; static int flawed; #define CONSTRAINT_PAGE_AFTER_EOS 1 #define CONSTRAINT_MUXING_VIOLATED 2 static stream_set *create_stream_set(void) { stream_set *set = calloc(1, sizeof(stream_set)); set->streams = calloc(5, sizeof(stream_processor)); set->allocated = 5; set->used = 0; return set; } void oi_info(char *format, ...) { va_list ap; if(!printinfo) return; va_start(ap, format); vfprintf(stdout, format, ap); va_end(ap); } void oi_warn(char *format, ...) { va_list ap; flawed = 1; if(!printwarn) return; va_start(ap, format); vfprintf(stdout, format, ap); va_end(ap); } void oi_error(char *format, ...) { va_list ap; flawed = 1; va_start(ap, format); vfprintf(stdout, format, ap); va_end(ap); } #define READ_U32_BE(buf) \ (((buf)[0]<<24)|((buf)[1]<<16)|((buf)[2]<<8)|((buf)[3]&0xff)) void check_xiph_comment(stream_processor *stream, int i, const char *comment, int comment_length) { char *sep = strchr(comment, '='); int j; int broken = 0; unsigned char *val; int bytes; int remaining; if(sep == NULL) { oi_warn(_("WARNING: Comment %d in stream %d has invalid " "format, does not contain '=': \"%s\"\n"), i, stream->num, comment); return; } for(j=0; j < sep-comment; j++) { if(comment[j] < 0x20 || comment[j] > 0x7D) { oi_warn(_("WARNING: Invalid comment fieldname in " "comment %d (stream %d): \"%s\"\n"), i, stream->num, comment); broken = 1; break; } } if(broken) return; val = (unsigned char *)comment; j = sep-comment+1; while(j < comment_length) { remaining = comment_length - j; if((val[j] & 0x80) == 0) bytes = 1; else if((val[j] & 0x40) == 0x40) { if((val[j] & 0x20) == 0) bytes = 2; else if((val[j] & 0x10) == 0) bytes = 3; else if((val[j] & 0x08) == 0) bytes = 4; else if((val[j] & 0x04) == 0) bytes = 5; else if((val[j] & 0x02) == 0) bytes = 6; else { oi_warn(_("WARNING: Illegal UTF-8 sequence in " "comment %d (stream %d): length marker wrong\n"), i, stream->num); broken = 1; break; } } else { oi_warn(_("WARNING: Illegal UTF-8 sequence in comment " "%d (stream %d): length marker wrong\n"), i, stream->num); broken = 1; break; } if(bytes > remaining) { oi_warn(_("WARNING: Illegal UTF-8 sequence in comment " "%d (stream %d): too few bytes\n"), i, stream->num); broken = 1; break; } switch(bytes) { case 1: /* No more checks needed */ break; case 2: if((val[j+1] & 0xC0) != 0x80) broken = 1; if((val[j] & 0xFE) == 0xC0) broken = 1; break; case 3: if(!((val[j] == 0xE0 && val[j+1] >= 0xA0 && val[j+1] <= 0xBF && (val[j+2] & 0xC0) == 0x80) || (val[j] >= 0xE1 && val[j] <= 0xEC && (val[j+1] & 0xC0) == 0x80 && (val[j+2] & 0xC0) == 0x80) || (val[j] == 0xED && val[j+1] >= 0x80 && val[j+1] <= 0x9F && (val[j+2] & 0xC0) == 0x80) || (val[j] >= 0xEE && val[j] <= 0xEF && (val[j+1] & 0xC0) == 0x80 && (val[j+2] & 0xC0) == 0x80))) broken = 1; if(val[j] == 0xE0 && (val[j+1] & 0xE0) == 0x80) broken = 1; break; case 4: if(!((val[j] == 0xF0 && val[j+1] >= 0x90 && val[j+1] <= 0xBF && (val[j+2] & 0xC0) == 0x80 && (val[j+3] & 0xC0) == 0x80) || (val[j] >= 0xF1 && val[j] <= 0xF3 && (val[j+1] & 0xC0) == 0x80 && (val[j+2] & 0xC0) == 0x80 && (val[j+3] & 0xC0) == 0x80) || (val[j] == 0xF4 && val[j+1] >= 0x80 && val[j+1] <= 0x8F && (val[j+2] & 0xC0) == 0x80 && (val[j+3] & 0xC0) == 0x80))) broken = 1; if(val[j] == 0xF0 && (val[j+1] & 0xF0) == 0x80) broken = 1; break; /* 5 and 6 aren't actually allowed at this point */ case 5: broken = 1; break; case 6: broken = 1; break; } if(broken) { char *simple = malloc(comment_length + 1); char *seq = malloc(comment_length * 3 + 1); static char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; int k, c1 = 0, c2 = 0; for (k = 0; k < comment_length; k++) { seq[c1++] = hex[((unsigned char)comment[k]) >> 4]; seq[c1++] = hex[((unsigned char)comment[k]) & 0xf]; seq[c1++] = ' '; if(comment[k] < 0x20 || comment[k] > 0x7D) simple[c2++] = '?'; else simple[c2++] = comment[k]; } seq[c1] = 0; simple[c2] = 0; oi_warn(_("WARNING: Illegal UTF-8 sequence in comment " "%d (stream %d): invalid sequence \"%s\": %s\n"), i, stream->num, simple, seq); broken = 1; free(simple); free(seq); break; } j += bytes; } if(sep - comment == 22 && oi_strncasecmp(comment, "METADATA_BLOCK_PICTURE", 22) == 0) { ogg_uint32_t picture_type; ogg_uint32_t mime_type_length; ogg_uint32_t description_length; ogg_uint32_t width; ogg_uint32_t height; ogg_uint32_t depth; ogg_uint32_t colors; ogg_uint32_t image_length; ogg_uint32_t file_width; ogg_uint32_t file_height; ogg_uint32_t file_depth; ogg_uint32_t file_colors; unsigned char *data; int data_sz; int len; int is_url; int format; int has_palette; int colors_set; len=comment_length - (sep+1-comment); /*Decode the Base64 encoded data.*/ if(len&3) { oi_warn(_("WARNING: Illegal Base64 length in " "METADATA_BLOCK_PICTURE comment %d (stream %d): %i is not " "divisible by 4\n"), i, stream->num, len); } len>>=2; data_sz=3*len; if(data_sz > 0) { if(comment[comment_length - 1] == '=') { data_sz--; } if(comment[comment_length - 2] == '=') { data_sz--; } } data=(unsigned char *)malloc(data_sz*sizeof(*data)); for (j = 0; j < len; j++) { ogg_uint32_t value; int k; value = 0; for (k = 1; k <= 4; k++) { unsigned c; unsigned d; c = (unsigned char)sep[4*j+k]; if(c == '+') { d = 62; } else if(c == '/') { d = 63; } else if(c >= '0' && c <= '9') { d = 52+c-'0'; } else if(c >= 'a' && c <= 'z') { d = 26+c-'a'; } else if(c >= 'A' && c <= 'Z') { d = c-'A'; } else if(c == '=') { if(3*j+k-1 < data_sz) { oi_warn(_("WARNING: Terminating '=' in illegal " "position in Base64 encoded " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "%i characters before the end.\n"), i, stream->num, data_sz - (3*j+k-1)); free(data); return; } d = 0; } else { oi_warn(_("WARNING: Illegal Base64 character in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "'%c' (0x%02X)\n"), i, stream->num, (char)(c<0x20||c>0x7E?'?':c), c); free(data); return; } value = value << 6 | d; } data[3*j] = (unsigned char)(value>>16); if(3*j+1 < data_sz) { data[3*j+1] = (unsigned char)(value>>8); if(3*j+2 < data_sz) { data[3*j+2] = (unsigned char)value; } } } /*Now validate the METADATA_BLOCK_PICTURE structure.*/ if(data_sz < 32) { oi_warn(_("WARNING: Not enough data for " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "expected at least 32 bytes, got %i\n"), i, stream->num, data_sz); free(data); return; } j = 0; picture_type = READ_U32_BE(data+j); if(picture_type > 20) { oi_warn(_("WARNING: Unknown picture type in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "%li\n"), i, stream->num, (long)picture_type); broken = 1; } if(picture_type >= 1 && picture_type <= 2) { if(stream->seen_file_icons & picture_type) { oi_warn(_("WARNING: Duplicate picture type in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " " %s\n"), i, stream->num, picture_type == 1 ? _("only one picture of type 1 (32x32 icon) allowed") : _("only one picture of type 2 (icon) allowed")); broken = 1; } stream->seen_file_icons |= picture_type; } j += 4; mime_type_length = READ_U32_BE(data+j); if(mime_type_length > (size_t)data_sz-32) { oi_warn(_("WARNING: Invalid media type length in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "%lu bytes when %i are available\n"), i, stream->num, (long)mime_type_length, data_sz-32); free(data); return; } for (j += 4; j < 8+(int)mime_type_length; j++) { if(data[j] < 0x20 || data[j] > 0x7E) { oi_warn(_("WARNING: Invalid character in media type of " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "0x%02X\n"), i, stream->num, data[j]); broken = 1; } } description_length = READ_U32_BE(data+j); if(description_length > (size_t)data_sz-mime_type_length-32) { oi_warn(_("WARNING: Invalid description length in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "%lu bytes when %i are available\n"), i, stream->num, (long)description_length, data_sz-mime_type_length-32); free(data); return; } /*TODO: Validate that description is UTF-8.*/ j += 4+description_length; width = READ_U32_BE(data+j); j += 4; height = READ_U32_BE(data+j); j += 4; depth = READ_U32_BE(data+j); j += 4; colors = READ_U32_BE(data+j); j += 4; /*If any value is non-zero, then they all MUST be valid values, and so colors should be treated as set (even if zero).*/ colors_set = width != 0 || height != 0 || depth != 0 || colors != 0; /*This isn't triggered if colors == 0, since that can be a valid value.*/ if((width == 0 || height == 0 || depth == 0) && colors_set) { oi_warn(_("WARNING: Invalid picture parameters in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "width (%i), height (%i), depth (%i), and colors (%i) MUST " "either be set to valid values or all set to 0\n"), i, stream->num, (int)width, (int)height, (int)depth, (int)colors); broken = 1; } image_length = READ_U32_BE(data+j); j += 4; /*This one should match exactly.*/ if(image_length != (size_t)data_sz-j) { oi_warn(_("WARNING: Invalid image data size in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "%lu bytes when %i are available\n"), i, stream->num, (long)image_length, data_sz-j); free(data); return; } is_url = 0; format = -1; if(mime_type_length == 10 && oi_strncasecmp((const char*)data+8, "image/jpeg", mime_type_length) == 0) { if(!is_jpeg(data+j, image_length)) { oi_warn(_("WARNING: Invalid image data in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "media type is %.*s but image does not appear to be " "JPEG\n"), i, stream->num, mime_type_length, data+8); free(data); return; } format = PIC_FORMAT_JPEG; } else if(mime_type_length == 9 && oi_strncasecmp((const char *)data+8, "image/png", mime_type_length) == 0) { if(!is_png(data+j, image_length)) { oi_warn(_("WARNING: Invalid image data in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "media type is %.*s but image does not appear to be " "PNG\n"), i, stream->num, mime_type_length, data+8); free(data); return; } format = PIC_FORMAT_PNG; } else if(mime_type_length == 9 && oi_strncasecmp((const char *)data+8, "image/gif", mime_type_length) == 0) { if(!is_gif(data+j, image_length)) { oi_warn(_("WARNING: Invalid image data in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "media type is %.*s but image does not appear to be " "PNG\n"), i, stream->num, mime_type_length, data+8); free(data); return; } format = PIC_FORMAT_GIF; } else if(mime_type_length == 3 && strncmp((const char *)data+8, "-->", mime_type_length) == 0) { is_url = 1; /*TODO: validate URL.*/ } else if(mime_type_length == 0 || (mime_type_length == 6 && oi_strncasecmp((const char *)data+8, "image/", mime_type_length) == 0)) { if(is_jpeg(data+j, image_length)) { format = PIC_FORMAT_JPEG; } else if(is_png(data+j, image_length)) { format = PIC_FORMAT_PNG; } else if(is_gif(data+j, image_length)) { format = PIC_FORMAT_GIF; } else { oi_warn(_("WARNING: Unknown image format in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "\"%.*s\" may not be well-supported\n"), i, stream->num, mime_type_length, data+8); } } else { oi_warn(_("WARNING: Unknown media type in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "\"%.*s\" may not be well-supported\n"), i, stream->num, mime_type_length, data+8); } file_width = file_height = file_depth = file_colors = 0; has_palette = -1; switch(format) { case PIC_FORMAT_JPEG: extract_jpeg_params(data+j, image_length, &file_width, &file_height, &file_depth, &file_colors, &has_palette); break; case PIC_FORMAT_PNG: extract_png_params(data+j, image_length, &file_width, &file_height, &file_depth, &file_colors, &has_palette); break; case PIC_FORMAT_GIF: extract_gif_params(data+j, image_length, &file_width, &file_height, &file_depth, &file_colors, &has_palette); break; } if(format >= 0 && has_palette < 0) { /*We should have been able to affirmatively determine whether or not there was a palette if we parsed the image successfully.*/ oi_warn(_("WARNING: Could not parse image parameters in" "METADATA_BLOCK_PICTURE comment %d (stream %d): " "possibly corrupt image?\n"), i, stream->num); broken = 1; } if(width && width != file_width) { oi_warn(_("WARNING: Mismatched picture parameters in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "width declared as %u but appears to be %u\n"), i, stream->num, (unsigned)width, (unsigned)file_width); broken = 1; } if(height && height != file_height) { oi_warn(_("WARNING: Mismatched picture parameters in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "height declared as %u but appears to be %u\n"), i, stream->num, (unsigned)height, (unsigned)file_height); broken = 1; } if(depth && depth != file_depth) { oi_warn(_("WARNING: Mismatched picture parameters in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "depth declared as %u but appears to be %u\n"), i, stream->num, (unsigned)depth, (unsigned)file_depth); broken = 1; } if(has_palette >= 0 && colors_set && colors != file_colors) { oi_warn(_("WARNING: Mismatched picture parameters in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "palette size declared as %u but appears to be %u\n"), i, stream->num, (unsigned)colors, (unsigned)file_colors); broken = 1; } if(picture_type == 1 && ((is_url && (width != 0 || height != 0) && (width != 32 || height != 32)) || (!is_url && (file_width != 32 || file_height != 32)))) { oi_warn(_("WARNING: Invalid picture in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "picture of type 1 (32x32 icon) MUST be a 32x32 PNG, but " "the image has dimensions %ux%u\n"), i, stream->num, (unsigned)is_url?width:file_width, (unsigned)is_url?height:file_height); broken = 1; } if(picture_type == 1 && !is_url && format != PIC_FORMAT_PNG) { oi_warn(_("WARNING: Invalid picture in " "METADATA_BLOCK_PICTURE comment %d (stream %d): " "picture of type 1 (32x32 icon) MUST be a 32x32 PNG, but " "the image does not appear to be a PNG\n"), i, stream->num); broken = 1; } /*Print the contents of the block using the same format as the SPECIFICATION argument to opusenc/flac/etc. (except without an image filename, since we don't know the original).*/ oi_info("\t%.*s%u|%.*s|%.*s|%ux%ux%u", (int)(sep+1-comment), comment, (unsigned)picture_type, mime_type_length, data+8, description_length, data+12+mime_type_length, (unsigned)width, (unsigned)height, (unsigned)depth); if(colors) { oi_info("/%u", (unsigned)colors); } if(is_url) { oi_info("|%.*s\n", image_length, data+j); } else { oi_info("|<%u bytes of image data>\n",(unsigned)image_length); } free(data); return; } if(!broken) { oi_info("\t%s\n", comment); } } static void process_null(stream_processor *stream, ogg_page *page) { /* This is for invalid streams. */ (void)stream; (void)page; } static void process_other(stream_processor *stream, ogg_page *page ) { ogg_packet packet; ogg_stream_pagein(&stream->os, page); while(ogg_stream_packetout(&stream->os, &packet) > 0) { /* Should we do anything here? Currently, we don't */ } } static void free_stream_set(stream_set *set) { int i; for(i=0; i < set->used; i++) { if(!set->streams[i].end) { oi_warn(_("WARNING: EOS not set on stream %d (normal for live streams)\n"), set->streams[i].num); if(set->streams[i].process_end) set->streams[i].process_end(&set->streams[i]); } ogg_stream_clear(&set->streams[i].os); } free(set->streams); free(set); } static int streams_open(stream_set *set) { int i; int res=0; for(i=0; i < set->used; i++) { if(!set->streams[i].end) res++; } return res; } static void null_start(stream_processor *stream) { stream->process_end = NULL; stream->type = "invalid"; stream->process_page = process_null; } static void other_start(stream_processor *stream, char *type) { if(type) stream->type = type; else stream->type = "unknown"; stream->process_page = process_other; stream->process_end = NULL; } static stream_processor *find_stream_processor(stream_set *set, ogg_page *page) { ogg_uint32_t serial = ogg_page_serialno(page); int i; int invalid = 0; int constraint = 0; stream_processor *stream; for(i=0; i < set->used; i++) { if(serial == set->streams[i].serial) { /* We have a match! */ stream = &(set->streams[i]); set->in_headers = 0; /* if we have detected EOS, then this can't occur here. */ if(stream->end) { stream->isillegal = 1; stream->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS; return stream; } stream->isnew = 0; stream->start = ogg_page_bos(page); stream->end = ogg_page_eos(page); stream->serial = serial; return stream; } } /* If there are streams open, and we've reached the end of the * headers, then we can't be starting a new stream. * XXX: might this sometimes catch ok streams if EOS flag is missing, * but the stream is otherwise ok? */ if(streams_open(set) && !set->in_headers) { constraint = CONSTRAINT_MUXING_VIOLATED; invalid = 1; } set->in_headers = 1; if(set->allocated < set->used) stream = &set->streams[set->used]; else { set->allocated += 5; set->streams = realloc(set->streams, sizeof(stream_processor)* set->allocated); stream = &set->streams[set->used]; } set->used++; stream->num = set->used; /* We count from 1 */ stream->isnew = 1; stream->isillegal = invalid; stream->constraint_violated = constraint; stream->seen_file_icons = 0; { int res; int has_oi_supported=0; ogg_packet packet; /* We end up processing the header page twice, but that's ok. */ ogg_stream_init(&stream->os, serial); ogg_stream_pagein(&stream->os, page); res = ogg_stream_packetout(&stream->os, &packet); if(res <= 0) { oi_warn(_("WARNING: Invalid header page, no packet found\n")); null_start(stream); } else if(packet.bytes >= 19 && memcmp(packet.packet, "OpusHead", 8)==0) info_opus_start(stream); else if(packet.bytes >= 7 && memcmp(packet.packet, "\x01vorbis", 7)==0) { other_start(stream, "Vorbis"); has_oi_supported=1; } else if(packet.bytes >= 7 && memcmp(packet.packet, "\x80theora", 7)==0) { other_start(stream, "Theora"); has_oi_supported=1; } else if(packet.bytes >= 8 && memcmp(packet.packet, "OggMIDI\0", 8)==0) other_start(stream, "MIDI"); else if(packet.bytes >= 5 && memcmp(packet.packet, "\177FLAC", 5)==0) other_start(stream, "FLAC"); else if(packet.bytes == 4 && memcmp(packet.packet, "fLaC", 4)==0) other_start(stream, "FLAC (legacy)"); else if(packet.bytes >= 8 && memcmp(packet.packet, "Speex ", 8)==0) other_start(stream, "speex"); else if(packet.bytes >= 8 && memcmp(packet.packet, "fishead\0", 8)==0) other_start(stream, "skeleton"); else if(packet.bytes >= 5 && memcmp(packet.packet, "BBCD\0", 5)==0) other_start(stream, "dirac"); else if(packet.bytes >= 8 && memcmp(packet.packet, "KW-DIRAC", 8)==0) other_start(stream, "dirac (legacy)"); else if(packet.bytes >= 8 && memcmp(packet.packet, "\x80kate\0\0\0", 8)==0) { other_start(stream, "Kate"); has_oi_supported=1; } else other_start(stream, NULL); res = ogg_stream_packetout(&stream->os, &packet); if(res > 0) { oi_warn(_("WARNING: Invalid header page in stream %d, " "contains multiple packets\n"), stream->num); } if(has_oi_supported)oi_info(_("Use ogginfo for more information on this file.\n")); /* re-init, ready for processing */ ogg_stream_clear(&stream->os); ogg_stream_init(&stream->os, serial); } stream->start = ogg_page_bos(page); stream->end = ogg_page_eos(page); stream->serial = serial; stream->shownillegal = 0; stream->seqno = ogg_page_pageno(page); if(stream->serial == 0 || stream->serial == 0xFFFFFFFFUL) { oi_info(_("Note: Stream %d has serial number %d, which is legal but may " "cause problems with some tools.\n"), stream->num, stream->serial); } return stream; } static int get_next_page(FILE *f, ogg_sync_state *ogsync, ogg_page *page, ogg_int64_t *written) { int ret; char *buffer; size_t bytes; while((ret = ogg_sync_pageseek(ogsync, page)) <= 0) { if(ret < 0) { /* unsynced, we jump over bytes to a possible capture - we don't need to read more just yet */ oi_warn(_("WARNING: Hole in data (%d bytes) found at approximate offset %" PRId64 " bytes. Corrupted Ogg.\n"), -ret, *written); continue; } /* zero return, we didn't have enough data to find a whole page, read */ buffer = ogg_sync_buffer(ogsync, CHUNK); bytes = fread(buffer, 1, CHUNK, f); if(bytes == 0) { ogg_sync_wrote(ogsync, 0); return 0; } ogg_sync_wrote(ogsync, (long)bytes); *written += bytes; } return 1; } static void process_file(char *filename) { FILE *file = fopen_utf8(filename, "rb"); ogg_sync_state ogsync; ogg_page page; stream_set *processors = create_stream_set(); int gotpage = 0; ogg_int64_t written = 0; if(!file) { oi_error(_("Error opening input file \"%s\": %s\n"), filename, strerror(errno)); return; } printf(_("Processing file \"%s\"...\n\n"), filename); ogg_sync_init(&ogsync); while(get_next_page(file, &ogsync, &page, &written)) { stream_processor *p = find_stream_processor(processors, &page); gotpage = 1; if(!p) { oi_error(_("Could not find a processor for stream, bailing\n")); return; } if(p->isillegal && !p->shownillegal) { char *constraint; switch(p->constraint_violated) { case CONSTRAINT_PAGE_AFTER_EOS: constraint = _("Page found for stream after EOS flag"); break; case CONSTRAINT_MUXING_VIOLATED: constraint = _("Ogg muxing constraints violated, new " "stream before EOS of all previous streams"); break; default: constraint = _("Error unknown."); } oi_warn(_("WARNING: illegally placed page(s) for logical stream %d\n" "This indicates a corrupt Ogg file: %s.\n"), p->num, constraint); p->shownillegal = 1; /* If it's a new stream, we want to continue processing this page * anyway to suppress additional spurious errors */ if(!p->isnew) continue; } if(p->isnew) { oi_info(_("New logical stream (#%d, serial: %08x): type %s\n"), p->num, p->serial, p->type); if(!p->start) oi_warn(_("WARNING: stream start flag not set on stream %d\n"), p->num); } else if(p->start) oi_warn(_("WARNING: stream start flag found in mid-stream " "on stream %d\n"), p->num); if(p->seqno++ != ogg_page_pageno(&page)) { if(!p->lostseq) oi_warn(_("WARNING: sequence number gap in stream %d. Got page " "%ld when expecting page %ld. Indicates missing data.%s\n" ), p->num, ogg_page_pageno(&page), p->seqno - 1, p->seqno-1==2?_(" (normal for live streams)"):""); p->seqno = ogg_page_pageno(&page); p->lostseq = 1; } else p->lostseq = 0; if(!p->isillegal) { p->process_page(p, &page); if(p->end) { if(p->process_end) p->process_end(p); oi_info(_("Logical stream %d ended\n"), p->num); p->isillegal = 1; p->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS; } } } if(!gotpage) oi_error(_("ERROR: No Ogg data found in file \"%s\".\n" "Input probably not Ogg.\n"), filename); free_stream_set(processors); ogg_sync_clear(&ogsync); fclose(file); } static void version(void) { printf(_("opusinfo from %s %s\n"), PACKAGE_NAME, PACKAGE_VERSION); } static void usage(void) { version(); printf(_(" by the Xiph.Org Foundation (https://www.xiph.org/)\n\n")); printf(_("(c) 2003-2005 Michael Smith <msmith@xiph.org>\n" "(c) 2012 Gregory Maxwell <greg@xiph.org>\n\n" "Opusinfo is a fork of ogginfo from the vorbis-tools package\n" "which has been cut down to only support opus files.\n\n" "Usage: opusinfo [flags] file1.opus [file2.opus ... fileN.opus]\n" "Flags supported:\n" "\t-h Show this help message.\n" "\t-q Make less verbose. Once will remove detailed informative\n" "\t messages, twice will remove warnings.\n" "\t-v Make more verbose. This may enable more detailed checks\n" "\t for some stream types.\n")); printf(_("\t-V Output version information and exit.\n")); } int main(int argc, char **argv) { int f, ret; #ifdef WIN_UNICODE int argc_utf8; char **argv_utf8; (void)argc; (void)argv; init_console_utf8(); init_commandline_arguments_utf8(&argc_utf8, &argv_utf8); #endif if(argc_utf8 < 2) { fprintf(stdout, _("Usage: opusinfo [flags] file1.opus [file2.opus ... fileN.opus]\n" "\n" "opusinfo is a tool for printing information about Opus files\n" "and for diagnosing problems with them.\n" "Full help shown with \"opusinfo -h\".\n")); #ifdef WIN_UNICODE uninit_console_utf8(); #endif exit(1); } while((ret = getopt(argc_utf8, argv_utf8, "hqvV")) >= 0) { switch(ret) { case 'h': usage(); return 0; case 'V': version(); return 0; case 'v': verbose++; break; case 'q': verbose--; break; } } if(verbose > 1) printlots = 1; if(verbose < 1) printinfo = 0; if(verbose < 0) printwarn = 0; if(optind >= argc_utf8) { fprintf(stderr, _("No input files specified. \"opusinfo -h\" for help\n")); return 1; } ret = 0; for(f=optind; f < argc_utf8; f++) { flawed = 0; process_file(argv_utf8[f]); if(flawed != 0) ret = flawed; } #ifdef WIN_UNICODE free_commandline_arguments_utf8(&argc_utf8, &argv_utf8); uninit_console_utf8(); #endif return ret; }