avinfo.c 16.5 KB
Newer Older
1
2
/* -*- tab-width:4;c-file-style:"cc-mode"; -*- */
/*
3
 * avinfo.c -- Convert ffmpeg supported a/v files to  Ogg Theora / Vorbis
4
 * Copyright (C) 2003-2011 <j@v2v.cc>
5
 *
6
 *   gcc -o avinfo avinfo.c -DAVINFO `pkg-config --cflags --libs libavcodec libavformat`
7
 *
8
 * This program is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation, either version 2 of the License, or
11
12
13
14
15
16
17
18
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
19
 * along with This program.  If not, see <http://www.gnu.org/licenses/>.
20
21
 */

22
23
24
25
26
27
28
29
30
31
32
33
34
#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
#if !defined(_LARGEFILE_SOURCE)
#define _LARGEFILE_SOURCE
#endif
#if !defined(_LARGEFILE64_SOURCE)
#define _LARGEFILE64_SOURCE
#endif
#if !defined(_FILE_OFFSET_BITS)
#define _FILE_OFFSET_BITS 64
#endif

35
36
37
38
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
39
#include <ctype.h>
40
41
42
43
44
45
#include <getopt.h>
#include <math.h>
#include <errno.h>
#include <sys/stat.h>

#include "libavformat/avformat.h"
Jan Gerber's avatar
Jan Gerber committed
46
#include "libavutil/pixdesc.h"
47

48
#ifndef WIN32
49
50
51
#if !defined(off64_t)
#define off64_t off_t
#endif
52
#endif
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

#ifdef WIN32
#define fseeko fseeko64
#define ftello ftello64
#endif

#ifdef _BIG_ENDIAN
#define htonll(x) \
((((x) & 0xff00000000000000LL) >> 56) | \
(((x) & 0x00ff000000000000LL) >> 40) | \
(((x) & 0x0000ff0000000000LL) >> 24) | \
(((x) & 0x000000ff00000000LL) >> 8) | \
(((x) & 0x00000000ff000000LL) << 8) | \
(((x) & 0x0000000000ff0000LL) << 24) | \
(((x) & 0x000000000000ff00LL) << 40) | \
(((x) & 0x00000000000000ffLL) << 56))
#else
#define htonll(x) x
#endif

unsigned long long get_filesize(char const *filename) {
    unsigned long long size = 0;
    FILE *file = fopen(filename, "rb");
    if (file) {
        fseeko(file, 0, SEEK_END);
        size = ftello(file);
Jan Gerber's avatar
cleanup    
Jan Gerber committed
79
        fclose(file);
80
    }
81
    return size;
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
}

char const *fix_codec_name(char const *codec_name) {
    if (!strcmp(codec_name, "libschrodinger")) {
        codec_name = "dirac";
    }
    else if (!strcmp(codec_name, "vp6f")) {
        codec_name = "vp6";
    }
    else if (!strcmp(codec_name, "mpeg2video")) {
        codec_name = "mpeg2";
    }
    else if (!strcmp(codec_name, "mpeg1video")) {
        codec_name = "mpeg1";
    }
    else if (!strcmp(codec_name, "0x0000")) {
        codec_name = "mu-law";
    }
100
101
102
    else if (!strcmp(codec_name, "libvpx")) {
        codec_name = "vp8";
    }
103
104
105
   return codec_name;
}

Jan Gerber's avatar
Jan Gerber committed
106
107
108
109
110
char *replace_str_all(char *str, char *orig, char *rep) {
  const char buffer[4096];
  char *p, *p_str = str, *p_buffer = (char *)buffer;
  int len = strlen(str);
  strncpy(p_buffer, str, len);
111
  while ((p = strstr(p_str, orig))) {
Jan Gerber's avatar
Jan Gerber committed
112
113
114
115
116
117
118
    strncpy(p_buffer, p_str, p-p_str);
    p_buffer += (p-p_str);
    len = len - strlen(orig) + strlen(rep);    
    sprintf(p_buffer, "%s%s", rep, p+strlen(orig));
    p_str = p + strlen(orig);
    p_buffer += strlen(rep);
  }
Jan Gerber's avatar
Jan Gerber committed
119
120
  p = malloc(len+1);
  strncpy(p, buffer, len);
Jan Gerber's avatar
Jan Gerber committed
121
122
  p[len] = '\0';
  return p;
Jan Gerber's avatar
Jan Gerber committed
123
124
}

125
126
127
enum {
    JSON_STRING,
    JSON_INT,
128
    JSON_LONGLONG,
129
130
131
    JSON_FLOAT,
} JSON_TYPES;

132
133
134
135
136
137
138
static void do_indent(FILE *output, int indent) {
    int i;
    for (i = 0; i < indent; i++)
        fprintf(output, "  ");
}

void json_add_key_value(FILE *output, char *key, void *value, int type, int last, int indent) {
Jan Gerber's avatar
Jan Gerber committed
139
    char *p;
140
141
    
    do_indent(output, indent);
142
143
    switch(type) {
        case JSON_STRING:
Jan Gerber's avatar
Jan Gerber committed
144
            p = (char *)value;
Jan Gerber's avatar
Jan Gerber committed
145
146
            p = replace_str_all(p, "\\", "\\\\");
            p = replace_str_all(p, "\"", "\\\"");
147
            fprintf(output, "\"%s\": \"%s\"", key, p);
Jan Gerber's avatar
Jan Gerber committed
148
            free(p);
149
150
            break;
        case JSON_INT:
151
            fprintf(output, "\"%s\": %d", key, *(int *)value);
152
            break;
153
        case JSON_LONGLONG:
154
            fprintf(output, "\"%s\": %lld", key, *(unsigned long long *)value);
Jan Gerber's avatar
Jan Gerber committed
155
            break;
156
        case JSON_FLOAT:
157
            fprintf(output, "\"%s\": %f", key, *(float *)value);
158
159
            break;
    }
Jan Gerber's avatar
Jan Gerber committed
160
161
162
163
164
    if (last) {
        fprintf(output, "\n");
    } else {
        fprintf(output, ",\n");
    }
165
166
}

167
void json_codec_info(FILE *output, AVCodecContext *enc, int indent) {
168
169
170
171
172
    const char *codec_name;
    char buf1[32];
    int bitrate;
    AVRational display_aspect_ratio;

Jan Gerber's avatar
Jan Gerber committed
173
    codec_name  = avcodec_get_name(enc->codec_id);
174
175

    switch(enc->codec_type) {
176
    case AVMEDIA_TYPE_VIDEO:
177
        codec_name = fix_codec_name(codec_name);
178
        json_add_key_value(output, "codec", (void *)codec_name, JSON_STRING, 0, indent);
179
        if (enc->pix_fmt != PIX_FMT_NONE) {
Jan Gerber's avatar
Jan Gerber committed
180
            json_add_key_value(output, "pixel_format", (void *)av_get_pix_fmt_name(enc->pix_fmt), JSON_STRING, 0, indent);
181
182
        }
        if (enc->width) {
183
184
            json_add_key_value(output, "width", &enc->width, JSON_INT, 0, indent);
            json_add_key_value(output, "height", &enc->height, JSON_INT, 0, indent);
185
186
187
188
189
190
191
            if (enc->sample_aspect_ratio.num) {
                av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den,
                          enc->width*enc->sample_aspect_ratio.num,
                          enc->height*enc->sample_aspect_ratio.den,
                          1024*1024);
                snprintf(buf1, sizeof(buf1), "%d:%d",
                         enc->sample_aspect_ratio.num, enc->sample_aspect_ratio.den);
192
                json_add_key_value(output, "pixel_aspect_ratio", buf1, JSON_STRING, 0, indent);
193
194
                snprintf(buf1, sizeof(buf1), "%d:%d",
                         display_aspect_ratio.num, display_aspect_ratio.den);
195
                json_add_key_value(output, "display_aspect_ratio", buf1, JSON_STRING, 0, indent);
196
197
198
199
200
            }
        }
        bitrate = enc->bit_rate;
        if (bitrate != 0) {
            float t = (float)bitrate / 1000;
201
            json_add_key_value(output, "bitrate", &t, JSON_FLOAT, 0, indent);
202
203
        }
        break;
204
    case AVMEDIA_TYPE_AUDIO:
205
        codec_name = fix_codec_name(codec_name);
206
        json_add_key_value(output, "codec", (void *)codec_name, JSON_STRING, 0, indent);
207
        if (enc->sample_rate) {
208
            json_add_key_value(output, "samplerate", &enc->sample_rate, JSON_INT, 0, indent);
209
        }
210
        json_add_key_value(output, "channels", &enc->channels, JSON_INT, 0, indent);
211
212
213

        /* for PCM codecs, compute bitrate directly */
        switch(enc->codec_id) {
Jan Gerber's avatar
Jan Gerber committed
214
215
        case AV_CODEC_ID_PCM_F64BE:
        case AV_CODEC_ID_PCM_F64LE:
216
217
            bitrate = enc->sample_rate * enc->channels * 64;
            break;
Jan Gerber's avatar
Jan Gerber committed
218
219
220
221
222
223
        case AV_CODEC_ID_PCM_S32LE:
        case AV_CODEC_ID_PCM_S32BE:
        case AV_CODEC_ID_PCM_U32LE:
        case AV_CODEC_ID_PCM_U32BE:
        case AV_CODEC_ID_PCM_F32BE:
        case AV_CODEC_ID_PCM_F32LE:
224
225
            bitrate = enc->sample_rate * enc->channels * 32;
            break;
Jan Gerber's avatar
Jan Gerber committed
226
227
228
229
230
        case AV_CODEC_ID_PCM_S24LE:
        case AV_CODEC_ID_PCM_S24BE:
        case AV_CODEC_ID_PCM_U24LE:
        case AV_CODEC_ID_PCM_U24BE:
        case AV_CODEC_ID_PCM_S24DAUD:
231
232
            bitrate = enc->sample_rate * enc->channels * 24;
            break;
Jan Gerber's avatar
Jan Gerber committed
233
234
235
236
237
        case AV_CODEC_ID_PCM_S16LE:
        case AV_CODEC_ID_PCM_S16BE:
        case AV_CODEC_ID_PCM_S16LE_PLANAR:
        case AV_CODEC_ID_PCM_U16LE:
        case AV_CODEC_ID_PCM_U16BE:
238
239
            bitrate = enc->sample_rate * enc->channels * 16;
            break;
Jan Gerber's avatar
Jan Gerber committed
240
241
242
243
244
        case AV_CODEC_ID_PCM_S8:
        case AV_CODEC_ID_PCM_U8:
        case AV_CODEC_ID_PCM_ALAW:
        case AV_CODEC_ID_PCM_MULAW:
        case AV_CODEC_ID_PCM_ZORK:
245
246
247
248
249
250
251
252
            bitrate = enc->sample_rate * enc->channels * 8;
            break;
        default:
            bitrate = enc->bit_rate;
            break;
        }
        if (bitrate != 0) {
            float t = (float)bitrate / 1000;
253
            json_add_key_value(output, "bitrate", &t, JSON_FLOAT, 0, indent);
254
255
256
        }
        break;
    /*
257
    case AVMEDIA_TYPE_DATA:
258
259
260
        fprintf(output, "datacodec: %s\n", codec_name);
        bitrate = enc->bit_rate;
        break;
261
    case AVMEDIA_TYPE_SUBTITLE:
262
263
264
        fprintf(output, "subtitle: %s\n", codec_name);
        bitrate = enc->bit_rate;
        break;
265
    case AVMEDIA_TYPE_ATTACHMENT:
266
267
268
269
270
271
272
273
274
275
276
277
278
        fprintf(output, "attachment: : %s\n", codec_name);
        bitrate = enc->bit_rate;
        break;
    */
    default:
        //FIXME: ignore unkown for now
        /*
        snprintf(buf, buf_size, "Invalid Codec type %d", enc->codec_type);
        */
        return;
    }
}

Jan Gerber's avatar
Jan Gerber committed
279
static int utf8_validate (char *s, int n) {
280
281
282
283
284
285
286
287
288
289
  int i;
  int extra_bytes;
  int mask;

  i=0;
  while (i<n) {
    if (i < n-3 && (*(uint32_t *)(s+i) & 0x80808080) == 0) {
      i+=4;
      continue;
    }
290
    if (s[i] < 0) goto error;
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
    if (s[i] < 128) {
      i++;
      continue;
    }
    if ((s[i] & 0xe0) == 0xc0) {
      extra_bytes = 1;
      mask = 0x7f;
    } else if ((s[i] & 0xf0) == 0xe0) {
      extra_bytes = 2;
      mask = 0x1f;
    } else if ((s[i] & 0xf8) == 0xf0) {
      extra_bytes = 3;
      mask = 0x0f;
    } else {
      goto error;
    }
    if (i + extra_bytes >= n) goto error;
    while(extra_bytes--) {
      i++;
      if ((s[i] & 0xc0) != 0x80) goto error;
    }
    i++;
  }

error:
  return i == n;
}
Jan Gerber's avatar
Jan Gerber committed
318
319
320


void json_metadata(FILE *output, AVDictionary *m, int indent)
321
{
Jan Gerber's avatar
Jan Gerber committed
322
    int first = 1;
Jan Gerber's avatar
Jan Gerber committed
323
    AVDictionaryEntry *tag = NULL;
Jan Gerber's avatar
Jan Gerber committed
324
    while ((tag = av_dict_get(m, "", tag, AV_DICT_IGNORE_SUFFIX))) {
325
326
327
        if (strlen(tag->value) && utf8_validate (tag->value, strlen(tag->value))) {
            if (first) {
                first = 0;
Jan Gerber's avatar
Jan Gerber committed
328
                do_indent(output, indent);
329
                fprintf(output, "\"metadata\": {\n");
Jan Gerber's avatar
Jan Gerber committed
330
                do_indent(output, indent + 1);
331
            } else {
Jan Gerber's avatar
Jan Gerber committed
332
                do_indent(output, indent + 1);
333
                fprintf(output, ",");
334
            }
Jan Gerber's avatar
Jan Gerber committed
335
            json_add_key_value(output, tag->key, tag->value, JSON_STRING, 1, 0);
336
337
        }
    }
Jan Gerber's avatar
Jan Gerber committed
338
339
340
341
    if (!first) {
        do_indent(output, indent);
        fprintf(output, "},\n");
    }
Jan Gerber's avatar
Jan Gerber committed
342
343
344
}


Jan Gerber's avatar
Jan Gerber committed
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368

static void json_stream_format(FILE *output, AVFormatContext *ic, int i, int indent, int first, int type_filter) {
    static int _first = 1;
    char buf1[32];

    AVStream *st = ic->streams[i];

    if (first)
        _first = 1;

    if(st->codec->codec_type == type_filter){
        if (!_first)
            fprintf(output, ", ");
        _first = 0;
        fprintf(output, "{\n");

        json_codec_info(output, st->codec, indent + 1);
        if(st->codec->codec_type == AVMEDIA_TYPE_VIDEO){
            if (st->time_base.den && st->time_base.num && av_q2d(st->time_base) > 0.001) {
                snprintf(buf1, sizeof(buf1), "%d:%d",
                         st->time_base.den, st->time_base.num);
                json_add_key_value(output, "framerate", buf1, JSON_STRING, 0, indent + 1);
            } else {
                snprintf(buf1, sizeof(buf1), "%d:%d",
369
                         st->avg_frame_rate.num, st->avg_frame_rate.den);
Jan Gerber's avatar
Jan Gerber committed
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
                json_add_key_value(output, "framerate", buf1, JSON_STRING, 0, indent + 1);
            }
            if (st->sample_aspect_ratio.num && // default
                av_cmp_q(st->sample_aspect_ratio, st->codec->sample_aspect_ratio)) {
                AVRational display_aspect_ratio;
                av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den,
                          st->codec->width*st->sample_aspect_ratio.num,
                          st->codec->height*st->sample_aspect_ratio.den,
                          1024*1024);
                snprintf(buf1, sizeof(buf1), "%d:%d",
                         st->sample_aspect_ratio.num, st->sample_aspect_ratio.den);
                json_add_key_value(output, "pixel_aspect_ratio", buf1, JSON_STRING, 0, indent+1);
                snprintf(buf1, sizeof(buf1), "%d:%d",
                         display_aspect_ratio.num, display_aspect_ratio.den);
                json_add_key_value(output, "display_aspect_ratio", buf1, JSON_STRING, 0, indent+1);
            }
        }
        json_metadata(output, st->metadata, indent + 1);
        json_add_key_value(output, "id", &i, JSON_INT, 1, indent + 1);
        do_indent(output, indent-1);
        fprintf(output, "}");
391
392
393
    }
}

394
395
396
397
/* 
 * os hash
 * based on public domain example from
 * http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes   
398
399
 * 
 * plus modification for files < 64k, buffer is filled with file data and padded with 0
400
 */
401

402
403
404
405
406
unsigned long long gen_oshash(char const *filename) {
    FILE *file;
    int i;
    unsigned long long t1=0;
    unsigned long long buffer1[8192*2];
Jan Gerber's avatar
Jan Gerber committed
407
    int used = 8192*2;
408
409
    file = fopen(filename, "rb");
    if (file) {
410
        //add filesize
411
412
413
414
        fseeko(file, 0, SEEK_END);
        t1 = ftello(file);
        fseeko(file, 0, SEEK_SET);

415
        if(t1 < 65536) {
Jan Gerber's avatar
Jan Gerber committed
416
417
            used = t1/8;
            fread(buffer1, used, 8, file);
418
419
        } else {
            fread(buffer1, 8192, 8, file);
420
            fseeko(file, -65536, SEEK_END);
421
422
            fread(&buffer1[8192], 8192, 8, file); 
        }
Jan Gerber's avatar
Jan Gerber committed
423
        for (i=0; i < used; i++)
424
            t1+=htonll(buffer1[i]);
425
426
427
428
429
        fclose(file);
    }
    return t1;
}

430
void json_oshash(FILE *output, char const *filename, int indent) {
Jan Gerber's avatar
Jan Gerber committed
431
    char hash[32];
Jan Gerber's avatar
Jan Gerber committed
432
#ifdef WIN32
433
    sprintf(hash,"%016I64x", gen_oshash(filename));
Jan Gerber's avatar
Jan Gerber committed
434
435
#elif defined (__SVR4) && defined (__sun)
    sprintf(hash,"%016llx", gen_oshash(filename));
Jan Gerber's avatar
Jan Gerber committed
436
#else
437
    sprintf(hash,"%016qx", gen_oshash(filename));
Jan Gerber's avatar
Jan Gerber committed
438
#endif
Jan Gerber's avatar
cleanup    
Jan Gerber committed
439
440
    if (strcmp(hash,"0000000000000000") > 0)
        json_add_key_value(output, "oshash", (void *)hash, JSON_STRING, 0, indent);
441
442
443
}


444
445
446
/* "user interface" functions */
void json_format_info(FILE* output, AVFormatContext *ic, const char *url) {
    int i;
447
    unsigned long long filesize;
448
449

    fprintf(output, "{\n");
450
451
452
453
454
455
456
457
458
459
460
461
462
    if(ic) {
        if (ic->duration != AV_NOPTS_VALUE) {
            float secs;
            secs = (float)ic->duration / AV_TIME_BASE;
            json_add_key_value(output, "duration", &secs, JSON_FLOAT, 0, 1);
        } else {
            float t = -1;
            json_add_key_value(output, "duration", &t, JSON_FLOAT, 0, 1);
        }
        if (ic->bit_rate) {
            float t = (float)ic->bit_rate / 1000;
            json_add_key_value(output, "bitrate", &t, JSON_FLOAT, 0, 1);
        }
463

464
465
466
467
468
469
        do_indent(output, 1);
        fprintf(output, "\"video\": [");
        if(ic->nb_programs) {
            int j, k;
            for(j=0; j<ic->nb_programs; j++) {
                for(k=0; k<ic->programs[j]->nb_stream_indexes; k++)
470
                    json_stream_format(output, ic, ic->programs[j]->stream_index[k], 2, !k && !j, AVMEDIA_TYPE_VIDEO);
471
472
473
             }
        } else {
            for(i=0;i<ic->nb_streams;i++) {
474
                json_stream_format(output, ic, i, 2, !i, AVMEDIA_TYPE_VIDEO);
475
            }
476
        }
477
478
479
480
481
482
483
484
        fprintf(output, "],\n");

        do_indent(output, 1);
        fprintf(output, "\"audio\": [");
        if(ic->nb_programs) {
            int j, k;
            for(j=0; j<ic->nb_programs; j++) {
                for(k=0; k<ic->programs[j]->nb_stream_indexes; k++)
485
                    json_stream_format(output, ic, ic->programs[j]->stream_index[k], 2, !k && !j, AVMEDIA_TYPE_AUDIO);
486
487
488
             }
        } else {
            for(i=0;i<ic->nb_streams;i++) {
489
                json_stream_format(output, ic, i, 2, !i, AVMEDIA_TYPE_AUDIO);
490
            }
491
        }
492
        fprintf(output, "],\n");
Jan Gerber's avatar
Jan Gerber committed
493
        json_metadata(output, ic->metadata, 1);
494
495
496
    } else {
        json_add_key_value(output, "code", "badfile", JSON_STRING, 0, 1);
        json_add_key_value(output, "error", "file does not exist or has unknown format.", JSON_STRING, 0, 1);
497
498
499
    }
    json_oshash(output, url, 1);
    json_add_key_value(output, "path", (void *)url, JSON_STRING, 0, 1);
500

Jan Gerber's avatar
Jan Gerber committed
501
    filesize = get_filesize(url);
502
    json_add_key_value(output, "size", &filesize, JSON_LONGLONG, 1, 1);
503

504
505
506
    fprintf(output, "}\n");
}

507
#ifdef AVINFO
508
509
int main(int argc, char **argv) {
    char inputfile_name[255];
Jan Gerber's avatar
Jan Gerber committed
510
    AVFormatContext *context = NULL;
511
512
513
514
515
516
517
518
519
520
521
522
523
524
    FILE* output = stdout;
    
    avcodec_register_all();
    av_register_all();

    if(argc == 1) {
        fprintf(stderr, "usage: %s avfile [outputfile]\n", argv[0]);
        exit(1);
    }
    snprintf(inputfile_name, sizeof(inputfile_name),"%s", argv[1]);
    if(argc == 3) {
        output = fopen(argv[2], "w");
    }
    
Jan Gerber's avatar
Jan Gerber committed
525
526
    if (avformat_open_input(&context, inputfile_name, NULL, NULL) >= 0) {
        if (avformat_find_stream_info(context, NULL) >= 0) {
527
528
529
530
531
532
533
            json_format_info(output, context, inputfile_name);
        }
    }
    if(output != stdout) {
        fclose(output);
    }
}
534
#endif