You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1097 lines
35 KiB
C
1097 lines
35 KiB
C
/*
|
|
* tcprobe.c
|
|
*
|
|
* Copyright (C) Thomas Oestreich - June 2001
|
|
* updated by
|
|
* Francesco Romani - April 206
|
|
*
|
|
* This file is part of transcode, a video stream processing tool
|
|
*
|
|
* transcode is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* transcode 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
|
|
* along with GNU Make; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*/
|
|
|
|
#include "transcode.h"
|
|
#include "tcinfo.h"
|
|
#include "libtc/libtc.h"
|
|
#include "libtc/iodir.h"
|
|
#include "libtc/xio.h"
|
|
#include "libtc/ratiocodes.h"
|
|
#include "ioaux.h"
|
|
#include "tc.h"
|
|
#include "demuxer.h"
|
|
#include "dvd_reader.h"
|
|
#include "x11source.h"
|
|
|
|
#include <math.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
|
|
|
|
#define EXE "tcprobe"
|
|
|
|
int verbose = TC_INFO;
|
|
|
|
int bitrate = ABITRATE;
|
|
int binary_dump = 0;
|
|
|
|
void import_exit(int code)
|
|
{
|
|
if (verbose >= TC_DEBUG) {
|
|
tc_log_msg(EXE, "(pid=%d) exit (code %d)", (int) getpid(), code);
|
|
}
|
|
exit(code);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
* enc_bitrate: Print bitrate information about the source data.
|
|
*
|
|
* Parameters:
|
|
* frames: Number of frames in the source.
|
|
* fps: Frames per second of the source.
|
|
* abitrate: Audio bitrate (bits per second).
|
|
* discsize: User-specified disc size in bytes, or 0 for none.
|
|
* Return value:
|
|
* None.
|
|
* Notes:
|
|
* This function is copied from tcscan.c. Ideally, tcprobe should
|
|
* only print basic source information, and this extended information
|
|
* should be handled by tcscan (or alternatively, tcscan should be
|
|
* merged into tcprobe).
|
|
*/
|
|
|
|
static void enc_bitrate(long frames, double fps, int abitrate, double discsize)
|
|
{
|
|
static const int defsize[] = { 650, 700, 1300, 1400 };
|
|
double audiosize, videosize, vbitrate;
|
|
long time;
|
|
|
|
if (frames <= 0 || fps <= 0.0) {
|
|
return;
|
|
}
|
|
time = frames / fps;
|
|
audiosize = (double)abitrate/8 * time;
|
|
|
|
/* Print basic source information */
|
|
printf("V: %ld frames, %ld sec @ %.3f fps\n",
|
|
frames, time, fps);
|
|
printf("A: %.2f MB @ %d kbps\n",
|
|
audiosize/(1024*1024), abitrate/1000);
|
|
|
|
/* Print recommended bitrates for user-specified or default disc sizes */
|
|
if (discsize) {
|
|
videosize = discsize - audiosize;
|
|
vbitrate = videosize / time;
|
|
printf("USER CDSIZE: %4d MB | V: %6.1f MB @ %.1f kbps\n",
|
|
(int)floor(discsize/(1024*1024)), videosize/(1024*1024),
|
|
vbitrate);
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < sizeof(defsize) / sizeof(*defsize); i++) {
|
|
videosize = defsize[i] - audiosize;
|
|
vbitrate = videosize / time;
|
|
printf("USER CDSIZE: %4d MB | V: %6.1f MB @ %.1f kbps\n",
|
|
defsize[i], videosize/(1024*1024),
|
|
vbitrate);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
* we don't want to scan the full directory since it can be a HUGE number
|
|
* of entries (think to images -> clip transcoding [i.e. jpeg -> AVI])
|
|
*/
|
|
#define TC_SCAN_MAX_FILES 32
|
|
|
|
/* informations for one stream found in given directory */
|
|
typedef struct tcdirentryinfo_ TCDirEntryInfo;
|
|
struct tcdirentryinfo_ {
|
|
uint32_t magic;
|
|
size_t count;
|
|
/* how many times a file with this magic was found on directory? */
|
|
int fd;
|
|
/* file descriptor of the FIRST file encountered with this magic */
|
|
};
|
|
|
|
/*
|
|
* tc_entry_find_magic:
|
|
* find the element in an array of TCDirEntryInfo strucutes
|
|
* that has it's magic equals to a given magic id.
|
|
*
|
|
* Parameters:
|
|
* info: pointer to an array of TCDirEntryInfo strucutre to be scanned.
|
|
* size: number of elements in given array.
|
|
* magic: magic ID to find
|
|
* Return value:
|
|
* -1: given magic ID not found on given array.
|
|
* >=0: index in array of element with same magic ID as given one.
|
|
*/
|
|
static int tc_entry_info_find_magic(const TCDirEntryInfo *infos,
|
|
size_t size, uint32_t magic)
|
|
{
|
|
int ret = -1;
|
|
size_t i = 0;
|
|
|
|
if (infos != NULL && size > 0) {
|
|
for (i = 0; i < size; i++) {
|
|
if (infos[i].magic == magic) {
|
|
ret = (int)i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
/*
|
|
* tc_entry_find_max_count:
|
|
* find the element in an array of TCDirEntryInfo strucutes
|
|
* that has the maximum count value.
|
|
*
|
|
* Parameters:
|
|
* info: pointer to an array of TCDirEntryInfo strucutre to be scanned.
|
|
* size: number of elements in given array.
|
|
* Return value:
|
|
* index in array of element with most high 'count' value.
|
|
*/
|
|
static int tc_entry_info_find_max_count(const TCDirEntryInfo *infos,
|
|
size_t size)
|
|
{
|
|
int ret = 0; /* start pointing to first element */
|
|
size_t i = 0;
|
|
|
|
if (infos != NULL && size > 0) {
|
|
for (i = 1; i < size; i++) {
|
|
if (infos[i].count > infos[ret].count) {
|
|
ret = (int)i;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* tc_entry_info_free:
|
|
* release resources linked to a TCDirEntryInfo structure.
|
|
*
|
|
* Parameters:
|
|
* de: pointer to a TCDirEntryInfo to be released.
|
|
* Return value:
|
|
* None
|
|
*/
|
|
static void tc_entry_info_free(TCDirEntryInfo *de)
|
|
{
|
|
if (de != NULL) {
|
|
xio_close(de->fd);
|
|
de->fd = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* tc_scan_directory_info:
|
|
* Partially scan a given directory and optionally filla TCDirEntryInfo
|
|
* structure with data about most common stream format found in.
|
|
*
|
|
* Partial scanning is done in order to avoid to waste too much
|
|
* time/resources in scanning phase. Anyway, partial scan ti's supposed
|
|
* to give results reliable enough.
|
|
* Filled TCDirEntryInfo structure will have an already open file
|
|
* descriptor pointing to the first file of the biggest set of files
|
|
* with the same magic id.
|
|
*
|
|
* Use tc_entry_info_free() to release resources acquired using this
|
|
* function, do not free()/close() things by hand to avoid undefined
|
|
* behaviours.
|
|
*
|
|
* Parameters:
|
|
* dname: path of dicrectory to scan.
|
|
* candidate: optional pointer of TCDirEntryInfo structure to be filled.
|
|
* can safely be NULL.
|
|
* Return value:
|
|
* -1: internal error
|
|
* 0: succesfull, but directory seems to have mixed content
|
|
* 1: succesfull, and directory seems to have homogeneous content
|
|
* Side effects:
|
|
* Some files (first TC_SCAN_MAX_FILES in filesystem order) in directory
|
|
* are open and scanned to detect their magic number.
|
|
*/
|
|
static int tc_scan_directory_info(const char *dname,
|
|
TCDirEntryInfo *candidate)
|
|
{
|
|
TCDirEntryInfo dinfo[TC_SCAN_MAX_FILES];
|
|
struct dirent *entry = NULL;
|
|
size_t i = 0, j = 0, last = 0, probed = 0;
|
|
int ret = -1;
|
|
DIR *dir = opendir(dname);
|
|
|
|
/* base sanity check first */
|
|
if (dir == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
/* round one: collect some stuff */
|
|
for (i = 0; i < TC_SCAN_MAX_FILES; i++) {
|
|
char path_buf[PATH_MAX + 1];
|
|
uint32_t magic = TC_MAGIC_UNKNOWN;
|
|
struct stat stat_buf;
|
|
int fd = -1, err;
|
|
|
|
entry = readdir(dir);
|
|
if (entry == NULL) {
|
|
break;
|
|
}
|
|
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
|
|
/* it's safe to skip them */
|
|
continue;
|
|
}
|
|
|
|
tc_snprintf(path_buf, sizeof(path_buf), "%s/%s",
|
|
dname, entry->d_name);
|
|
err = stat(path_buf, &stat_buf);
|
|
if (err || !S_ISREG(stat_buf.st_mode)) {
|
|
if (verbose >= TC_DEBUG) { /* uhm */
|
|
tc_log_warn(EXE, "opening '%s': is not a file",
|
|
path_buf);
|
|
}
|
|
continue;
|
|
}
|
|
fd = xio_open(path_buf, O_RDONLY);
|
|
if (fd == -1) {
|
|
/* switch to tc_log_perror? */
|
|
tc_log_error(EXE, "opening '%s': %s",
|
|
path_buf, strerror(errno));
|
|
continue; /* assume non-fatal error */
|
|
}
|
|
magic = fileinfo(fd, 0);
|
|
j = tc_entry_info_find_magic(dinfo, last, magic);
|
|
if (j != -1) { /* entry already encountered */
|
|
dinfo[j].count++;
|
|
xio_close(fd);
|
|
/* we want only the first file descriptor of a given set */
|
|
} else {
|
|
dinfo[last].fd = fd;
|
|
dinfo[last].magic = magic;
|
|
dinfo[last].count = 1;
|
|
last++;
|
|
}
|
|
probed++;
|
|
}
|
|
closedir(dir);
|
|
|
|
/* round two: let's make a choice */
|
|
if (last > 0) { /* at least one file scanned succesfully */
|
|
if (last == 1) {
|
|
j = 0; /* pretty simple, uh? ;) */
|
|
ret = 1; /* obviously homogeneous */
|
|
} else {
|
|
j = tc_entry_info_find_max_count(dinfo, last);
|
|
/* save only candidate entry info */
|
|
for (i = 0; i < last; i++) {
|
|
if (i != j) {
|
|
tc_entry_info_free(&dinfo[i]);
|
|
}
|
|
}
|
|
if (dinfo[j].count < probed + 1) {
|
|
ret = 0;
|
|
} else {
|
|
ret = 1;
|
|
}
|
|
}
|
|
if (candidate != NULL) {
|
|
candidate->fd = dinfo[j].fd;
|
|
candidate->magic = dinfo[j].magic;
|
|
candidate->count = dinfo[j].count;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
#define OPEN_FILE(ipipe, name) do { \
|
|
(ipipe)->fd_in = xio_open((name), O_RDONLY); \
|
|
if ((ipipe)->fd_in < 0) { \
|
|
tc_log_perror(EXE, "file open"); \
|
|
return TC_IMPORT_ERROR; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define PROBE_DIR(ipipe) do { \
|
|
TCDirEntryInfo info; \
|
|
int ret = tc_scan_directory_info((ipipe)->name, &info); \
|
|
if (ret < 0) { \
|
|
tc_log_error(EXE, "unrecognized filetype for '%s'", \
|
|
(ipipe)->name); \
|
|
return TC_IMPORT_ERROR; \
|
|
} else if (ret == 0) { \
|
|
tc_log_warn(EXE, "non-homogeneous directory content" \
|
|
" (different stream type detected)"); \
|
|
} \
|
|
(ipipe)->fd_in = info.fd; \
|
|
(ipipe)->magic = info.magic; \
|
|
} while (0)
|
|
|
|
/*
|
|
* info_setup:
|
|
*
|
|
* perform second-step initialization on a info_t structure.
|
|
* While first-step setup can be done statically with simple
|
|
* assignements, intialization performedon this stage is based
|
|
* on data given previosuly (i.e.: name).
|
|
* This function is a catch-all for all the black magic things.
|
|
* Of course there is still a lot of room for cleaning up and
|
|
* refactoring.
|
|
* My thought is that doing things in a _really_ clean way means
|
|
* rewriting almoost from the ground up the probing infrastructure.
|
|
*
|
|
* Parameters:
|
|
* ipipe: info_t structure to (finish to) initialize
|
|
* skip: amount of bytes to skip when analyzing a regular file.
|
|
* Ignored otherwise
|
|
* mplayer_probe: if input it's a regular file, do the real probing through
|
|
* mplayer (if avalaible); ignored otherwise.
|
|
* want_dvd: if !0 and the source looks likea DVD, handle it like a DVD.
|
|
* I know this is a bit obscure and maybe even sick, it's
|
|
* legacy and should go away in the future.
|
|
* Return Value:
|
|
* TC_IMPORT_OK -> succesfull,
|
|
* TC_IMPORT_ERROR -> otherwise.
|
|
* messages are sent to user using tc_log_*() in both cases.
|
|
* Side effects:
|
|
* quite a lot =)
|
|
* Input source is open and read to guess the source type.
|
|
* This function can do (and usually does) several read attempts.
|
|
* Preconditions:
|
|
* given info_t structure is already basically initialized (see
|
|
* first-step setup above). This measn set at least:
|
|
* ipipe.verbose, ipipe.fd_in, ipipe.name = name;
|
|
* Postconditions:
|
|
* given info_t is ready to be used in probe_stream()
|
|
*
|
|
*/
|
|
static int info_setup(info_t *ipipe, int skip, int mplayer_probe, int want_dvd)
|
|
{
|
|
int file_kind = tc_probe_path(ipipe->name);
|
|
|
|
switch (file_kind) {
|
|
case TC_PROBE_PATH_FILE: /* regular file */
|
|
if (mplayer_probe) {
|
|
ipipe->magic = TC_MAGIC_MPLAYER;
|
|
} else if (want_dvd && dvd_is_valid(ipipe->name)) {
|
|
ipipe->magic = TC_MAGIC_DVD;
|
|
} else {
|
|
OPEN_FILE(ipipe, ipipe->name);
|
|
ipipe->magic = fileinfo(ipipe->fd_in, skip);
|
|
ipipe->seek_allowed = 1;
|
|
}
|
|
break;
|
|
case TC_PROBE_PATH_RELDIR: /* relative path to directory */
|
|
PROBE_DIR(ipipe);
|
|
break;
|
|
case TC_PROBE_PATH_ABSPATH: /* absolute path */
|
|
if (dvd_is_valid(ipipe->name)) {
|
|
ipipe->magic = TC_MAGIC_DVD;
|
|
} else {
|
|
PROBE_DIR(ipipe);
|
|
}
|
|
break;
|
|
/* now the easy stuff */
|
|
case TC_PROBE_PATH_BKTR: /* bktr device */
|
|
ipipe->magic = TC_MAGIC_BKTR_VIDEO;
|
|
break;
|
|
case TC_PROBE_PATH_SUNAU: /* sunau device */
|
|
ipipe->magic = TC_MAGIC_SUNAU_AUDIO;
|
|
break;
|
|
case TC_PROBE_PATH_OSS: /* OSS device */
|
|
ipipe->magic = TC_MAGIC_OSS_AUDIO;
|
|
break;
|
|
case TC_PROBE_PATH_V4L_VIDEO: /* v4l video device */
|
|
ipipe->magic = TC_MAGIC_V4L_VIDEO;
|
|
break;
|
|
case TC_PROBE_PATH_V4L_AUDIO: /* v4l audio device */
|
|
ipipe->magic = TC_MAGIC_V4L_AUDIO;
|
|
break;
|
|
case TC_PROBE_PATH_INVALID: /* non-existent source */
|
|
default: /* fallthrough */
|
|
tc_log_error(EXE, "can't determine the file kind");
|
|
return TC_IMPORT_ERROR;
|
|
} /* probe_path */
|
|
return TC_IMPORT_OK;
|
|
}
|
|
|
|
#undef OPEN_FILE
|
|
#undef PROBE_DIR
|
|
|
|
/*
|
|
* info_teardown:
|
|
*
|
|
* reverse initialization done in info_setup
|
|
*
|
|
* Parameters:
|
|
* ipipe: info_t structure to (finish to) initialize
|
|
* Return Value:
|
|
* None
|
|
*/
|
|
static void info_teardown(info_t *ipipe)
|
|
{
|
|
if (ipipe->fd_in != STDIN_FILENO) {
|
|
xio_close(ipipe->fd_in);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* new fancy output handlers */
|
|
|
|
/*
|
|
* generic info dump function handler
|
|
*/
|
|
typedef void (*InfoDumpFn)(info_t *ipipe);
|
|
|
|
/*
|
|
* dump_info_binary:
|
|
*
|
|
* dump a ProbeInfo structure in binary (and platform-dependent,
|
|
* and probably even not fully safe) way to stdout.
|
|
* This dump mode is used by tcprobe to communicate with transcode.
|
|
* Legacy, I'd like to change this communication mode in future
|
|
* releases.
|
|
*
|
|
* Parameters:
|
|
* ipipe: info_t structure holding the ProbeInfo data to dump.
|
|
* Return Value:
|
|
* None
|
|
*/
|
|
static void dump_info_binary(info_t *ipipe)
|
|
{
|
|
pid_t pid = getpid();
|
|
tc_pwrite(STDOUT_FILENO, (uint8_t *) &pid, sizeof(pid_t));
|
|
tc_pwrite(STDOUT_FILENO, (uint8_t *) ipipe->probe_info,
|
|
sizeof(ProbeInfo));
|
|
}
|
|
|
|
|
|
#define PROBED_NEW "(*)" /* value different from tc's defaults */
|
|
#define PROBED_STD "" /* value equals to tc's defaults */
|
|
|
|
/*
|
|
* user mode:
|
|
* recommended transcode command line options:
|
|
*/
|
|
#define MARK_EXPECTED(ex) ((ex) ?(PROBED_STD) :(PROBED_NEW))
|
|
#define CHECK_MARK_EXPECTED(probed, val) \
|
|
(((val) == (probed)) ?(PROBED_STD) :(PROBED_NEW))
|
|
|
|
/*
|
|
* dump_info_old:
|
|
* dump a ProbeInfo structure in a human-readable format.
|
|
*
|
|
* Parameters:
|
|
* ipipe: info_t structure holding the ProbeInfo data to dump.
|
|
* Return Value:
|
|
* None
|
|
*/
|
|
|
|
static void dump_info_old(info_t *ipipe)
|
|
{
|
|
long frame_time = 0;
|
|
int is_std = TC_TRUE; /* flag: select PROBED_??? above */
|
|
int nsubs = 0, i = 0;
|
|
char extrabuf[TC_BUF_MIN] = { '\0' };
|
|
int extrabuf_ready = TC_FALSE;
|
|
size_t len = 0;
|
|
|
|
/* full-blown back-compatibility */
|
|
fprintf(stderr, "[%s] %s\n", EXE, filetype(ipipe->magic));
|
|
printf("[%s] summary for %s, %s = not default, 0 = not detected\n",
|
|
EXE, ((ipipe->magic == TC_STYPE_STDIN) ?"-" :ipipe->name),
|
|
PROBED_NEW);
|
|
|
|
if (ipipe->probe_info->width != PAL_W
|
|
|| ipipe->probe_info->height != PAL_H) {
|
|
is_std = TC_FALSE;
|
|
}
|
|
|
|
/* video first. */
|
|
if (ipipe->probe_info->width > 0 && ipipe->probe_info->height > 0) {
|
|
int n, d, ret;
|
|
|
|
extrabuf_ready = TC_FALSE;
|
|
|
|
printf("%18s %s %dx%d [%dx%d] %s\n",
|
|
"import frame size:", "-g",
|
|
ipipe->probe_info->width, ipipe->probe_info->height,
|
|
PAL_W, PAL_H, MARK_EXPECTED(is_std));
|
|
|
|
ret = tc_asr_code_to_ratio(ipipe->probe_info->asr, &n, &d);
|
|
if (ret != TC_NULL_MATCH && (n > 0 && d > 0)) {
|
|
/* back compatibility little hack */
|
|
printf("%18s %i:%i %s\n",
|
|
"aspect ratio:", n, d,
|
|
CHECK_MARK_EXPECTED(ipipe->probe_info->asr, 1));
|
|
}
|
|
|
|
frame_time = (ipipe->probe_info->fps != 0) ?
|
|
(long)(1. / ipipe->probe_info->fps * 1000) : 0;
|
|
|
|
printf("%18s %s %.3f [%.3f] frc=%d %s\n", "frame rate:", "-f",
|
|
ipipe->probe_info->fps, PAL_FPS, ipipe->probe_info->frc,
|
|
CHECK_MARK_EXPECTED(ipipe->probe_info->frc, 3));
|
|
|
|
tc_snprintf(extrabuf, sizeof(extrabuf), "%18s ", "");
|
|
/* empty string to have a nice justification */
|
|
/* video track extra info */
|
|
if (ipipe->probe_info->pts_start) {
|
|
len = strlen(extrabuf);
|
|
tc_snprintf(extrabuf + len, sizeof(extrabuf) - len,
|
|
"PTS=%.4f, frame_time=%ldms",
|
|
ipipe->probe_info->pts_start, frame_time);
|
|
if (ipipe->probe_info->bitrate) {
|
|
len = strlen(extrabuf);
|
|
tc_snprintf(extrabuf + len, sizeof(extrabuf) - len,
|
|
"%sbitrate=%li kbps",
|
|
(extrabuf_ready) ?", " :" ",
|
|
/*
|
|
* add seeparator only if we alread
|
|
* written something in buffer
|
|
*/
|
|
ipipe->probe_info->bitrate);
|
|
}
|
|
/* at this point extrabuf flag will always be set to on */
|
|
extrabuf_ready = TC_TRUE;
|
|
}
|
|
if (extrabuf_ready) {
|
|
printf("%s\n", extrabuf);
|
|
}
|
|
}
|
|
|
|
/* audio next. */
|
|
for (i = 0; i < TC_MAX_AUD_TRACKS; i++) {
|
|
int D_arg = 0, D_arg_ms = 0;
|
|
double pts_diff = 0.;
|
|
|
|
if (ipipe->probe_info->track[i].format != 0
|
|
&& ipipe->probe_info->track[i].chan > 0) {
|
|
extrabuf_ready = TC_FALSE;
|
|
extrabuf[0] = '\0';
|
|
|
|
if (ipipe->probe_info->track[i].samplerate != RATE
|
|
|| ipipe->probe_info->track[i].chan != CHANNELS
|
|
|| ipipe->probe_info->track[i].bits != BITS
|
|
|| ipipe->probe_info->track[i].format != CODEC_AC3) {
|
|
is_std = TC_FALSE;
|
|
} else {
|
|
is_std = TC_TRUE;
|
|
}
|
|
|
|
printf("%18s -a %d [0] -e %d,%d,%d [%d,%d,%d] -n 0x%x [0x%x] %s\n",
|
|
"audio track:",
|
|
ipipe->probe_info->track[i].tid,
|
|
ipipe->probe_info->track[i].samplerate,
|
|
ipipe->probe_info->track[i].bits,
|
|
ipipe->probe_info->track[i].chan,
|
|
RATE, BITS, CHANNELS,
|
|
ipipe->probe_info->track[i].format,
|
|
CODEC_AC3,
|
|
MARK_EXPECTED(is_std));
|
|
|
|
/* audio track extra info */
|
|
if (ipipe->probe_info->track[i].pts_start) {
|
|
tc_snprintf(extrabuf, sizeof(extrabuf), "PTS=%.4f",
|
|
ipipe->probe_info->track[i].pts_start);
|
|
extrabuf_ready = TC_TRUE;
|
|
}
|
|
if (ipipe->probe_info->track[i].bitrate) {
|
|
size_t len = strlen(extrabuf);
|
|
tc_snprintf(extrabuf + len, sizeof(extrabuf) - len,
|
|
"%sbitrate=%i kbps", (extrabuf_ready) ?", " :"",
|
|
ipipe->probe_info->track[i].bitrate);
|
|
extrabuf_ready = TC_TRUE;
|
|
}
|
|
if (extrabuf_ready) {
|
|
printf("%18s %s\n",
|
|
"", /* empty string for a nice justification */
|
|
extrabuf);
|
|
}
|
|
|
|
/* audio track A/V sync suggestion */
|
|
if (ipipe->probe_info->pts_start > 0
|
|
&& ipipe->probe_info->track[i].pts_start > 0
|
|
&& ipipe->probe_info->fps != 0) {
|
|
pts_diff = ipipe->probe_info->pts_start \
|
|
- ipipe->probe_info->track[i].pts_start;
|
|
D_arg = (int)(pts_diff * ipipe->probe_info->fps);
|
|
D_arg_ms = (int)((pts_diff - D_arg/ipipe->probe_info->fps)*1000);
|
|
|
|
printf("%18s -D %d --av_fine_ms %d (frames & ms) [0] [0]\n",
|
|
" ", D_arg, D_arg_ms);
|
|
}
|
|
}
|
|
/* have subtitles here? */
|
|
if (ipipe->probe_info->track[i].attribute & PACKAGE_SUBTITLE) {
|
|
nsubs++;
|
|
}
|
|
}
|
|
|
|
/* no audio */
|
|
if (ipipe->probe_info->num_tracks == 0) {
|
|
printf("%18s %s", "no audio track:",
|
|
"(use \"null\" import module for audio)\n");
|
|
}
|
|
|
|
if (nsubs > 0) {
|
|
printf("detected (%d) subtitle(s)\n", nsubs);
|
|
}
|
|
|
|
/* P-units */
|
|
if (ipipe->probe_info->unit_cnt) {
|
|
printf("detected (%d) presentation unit(s) (SCR reset)\n",
|
|
ipipe->probe_info->unit_cnt+1);
|
|
}
|
|
|
|
/* DVD only: coder bitrate infos */
|
|
if (ipipe->magic == TC_MAGIC_DVD_PAL || ipipe->magic == TC_MAGIC_DVD_NTSC
|
|
|| ipipe->magic == TC_MAGIC_DVD) {
|
|
enc_bitrate((long)ceil(ipipe->probe_info->fps * ipipe->probe_info->time),
|
|
ipipe->probe_info->fps, bitrate*1000, 0);
|
|
} else {
|
|
if (ipipe->probe_info->frames > 0) {
|
|
unsigned long dur_ms;
|
|
unsigned int dur_h, dur_min, dur_s;
|
|
if (ipipe->probe_info->fps < 0.100) {
|
|
dur_ms = (long)ipipe->probe_info->frames*frame_time;
|
|
} else {
|
|
dur_ms = (long)((float)ipipe->probe_info->frames * 1000
|
|
/ipipe->probe_info->fps);
|
|
}
|
|
dur_h = dur_ms/3600000;
|
|
dur_min = (dur_ms %= 3600000)/60000;
|
|
dur_s = (dur_ms %= 60000)/1000;
|
|
dur_ms %= 1000;
|
|
printf("%18s %ld frames, frame_time=%ld msec,"
|
|
" duration=%u:%02u:%02u.%03lu\n",
|
|
"length:",
|
|
ipipe->probe_info->frames, frame_time,
|
|
dur_h, dur_min, dur_s, dur_ms);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* dump_track_info_raw:
|
|
*
|
|
* dump a ProbeTrackInfo structure in a human-readable but machine-friendly
|
|
* format, resembling, or identical where feasible, the mplayer -identify
|
|
* output.
|
|
* Print one field at line, in the format KEY=value.
|
|
*
|
|
* Parameters:
|
|
* tracks: pointer to ProbeTrackInfo array to be dumped
|
|
* i: dump only the i-th structure on array.
|
|
* Return Value:
|
|
* None
|
|
*/
|
|
static void dump_track_info_raw(ProbeTrackInfo *ti, int i)
|
|
{
|
|
if (ti != NULL && i >= 0) { /* paranoia */
|
|
const char *ext = "";
|
|
/* extension to identifiers for non-zero (not first) track */
|
|
char ext_buf[24];
|
|
|
|
if (i > 0) {
|
|
tc_snprintf(ext_buf, sizeof(ext_buf), "_%i", i);
|
|
ext = ext_buf;
|
|
}
|
|
|
|
if (ti[i].format != 0 && ti[i].chan > 0) {
|
|
printf("ID_AUDIO_CODEC%s=%s\n", ext,
|
|
tc_codec_to_string(ti[i].format));
|
|
printf("ID_AUDIO_FORMAT%s=%i\n", ext, ti[i].format);
|
|
printf("ID_AUDIO_BITRATE%s=%i\n", ext, ti[i].bitrate);
|
|
printf("ID_AUDIO_RATE%s=%i\n", ext, ti[i].samplerate);
|
|
printf("ID_AUDIO_NCH%s=%i\n", ext, ti[i].chan);
|
|
printf("ID_AUDIO_BITS%s=%i\n", ext, ti[i].bits);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* dump_info_raw:
|
|
*
|
|
* dump a ProbeInfo structure in a human-readable but machine-friendly
|
|
* format, resembling, or identical where feasible, the mplayer -identify
|
|
* output.
|
|
* Print one field at line, in the format KEY=value.
|
|
*
|
|
* Parameters:
|
|
* ipipe: info_t structure holding the ProbeInfo data to dump.
|
|
* Return Value:
|
|
* None
|
|
*/
|
|
static void dump_info_raw(info_t *ipipe)
|
|
{
|
|
int i;
|
|
double duration = 0.0; /* seconds */
|
|
|
|
/* general information */
|
|
printf("ID_FILENAME=\"%s\"\n", ipipe->name);
|
|
printf("ID_FILETYPE=\"%s\"\n", filetype(ipipe->magic));
|
|
|
|
/* video track, only the first */
|
|
printf("ID_VIDEO_WIDTH=%i\n", ipipe->probe_info->width);
|
|
printf("ID_VIDEO_HEIGHT=%i\n", ipipe->probe_info->height);
|
|
printf("ID_VIDEO_FPS=%.3f\n", ipipe->probe_info->fps);
|
|
printf("ID_VIDEO_FRC=%i\n", ipipe->probe_info->frc);
|
|
printf("ID_VIDEO_ASR=%i\n", ipipe->probe_info->asr);
|
|
printf("ID_VIDEO_FORMAT=%s\n",
|
|
tc_codec_to_string(ipipe->probe_info->codec));
|
|
printf("ID_VIDEO_BITRATE=%li\n", ipipe->probe_info->bitrate);
|
|
|
|
/* audio stuff */
|
|
for (i = 0; i < TC_MAX_AUD_TRACKS; i++) {
|
|
dump_track_info_raw(ipipe->probe_info->track, i);
|
|
}
|
|
|
|
if (ipipe->probe_info->fps != 0.0) {
|
|
/* seconds */
|
|
duration = ((double)ipipe->probe_info->frames/ipipe->probe_info->fps);
|
|
}
|
|
/* general information, reprise */
|
|
printf("ID_LENGTH=%.2f\n", duration);
|
|
}
|
|
|
|
/*
|
|
* dump_info_new:
|
|
* dump a ProbeInfo structure in new, better
|
|
* human-readable format.
|
|
*
|
|
* Parameters:
|
|
* ipipe: info_t structure holding the ProbeInfo data to dump.
|
|
* Return Value:
|
|
* None
|
|
*/
|
|
static void dump_info_new(info_t *ipipe)
|
|
{
|
|
int i = 0, j = 0;
|
|
unsigned long dur_ms = 0;
|
|
unsigned int dur_h = 0, dur_min = 0, dur_s = 0;
|
|
long frame_time = (ipipe->probe_info->fps != 0)
|
|
? (long)(1. / ipipe->probe_info->fps * 1000) : 0;
|
|
|
|
if (ipipe->probe_info->fps < 0.100) {
|
|
dur_ms = (long)ipipe->probe_info->frames * frame_time;
|
|
} else {
|
|
dur_ms = (long)((float)ipipe->probe_info->frames * 1000
|
|
/ipipe->probe_info->fps);
|
|
}
|
|
dur_h = dur_ms / 3600000;
|
|
dur_min = (dur_ms %= 3600000) / 60000;
|
|
dur_s = (dur_ms %= 60000) / 1000;
|
|
dur_ms %= 1000;
|
|
|
|
printf("* container:\n");
|
|
printf("%18s: %s\n", "format",
|
|
filetype(ipipe->probe_info->magic));
|
|
printf("%18s: '%s'\n", "source",
|
|
((ipipe->magic == TC_STYPE_STDIN) ?"-" :ipipe->name));
|
|
printf("%18s: %li\n", "frames",
|
|
ipipe->probe_info->frames);
|
|
printf("%18s: %u:%02u:%02u.%03lu\n", "duration",
|
|
dur_h, dur_min, dur_s, dur_ms);
|
|
printf("%18s: %i\n", "SCR reset",
|
|
ipipe->probe_info->unit_cnt + 1);
|
|
|
|
/* video first. */
|
|
if (ipipe->probe_info->width > 0 && ipipe->probe_info->height > 0) {
|
|
int n, d;
|
|
|
|
tc_asr_code_to_ratio(ipipe->probe_info->asr, &n, &d);
|
|
|
|
printf("* video track #0:\n");
|
|
printf("%18s: %s\n", "format",
|
|
tc_codec_to_string(ipipe->probe_info->codec));
|
|
printf("%18s: %ix%i\n", "frame size",
|
|
ipipe->probe_info->width, ipipe->probe_info->height);
|
|
printf("%18s: %i:%i (asr=%i)\n", "aspect ratio",
|
|
n, d, ipipe->probe_info->asr);
|
|
printf("%18s: %.3f (frc=%i)\n", "frame rate",
|
|
ipipe->probe_info->fps, ipipe->probe_info->frc);
|
|
printf("%18s: %li kbps\n", "bitrate", ipipe->probe_info->bitrate);
|
|
printf("%18s: %.4f\n", "starting PTS",
|
|
ipipe->probe_info->pts_start);
|
|
printf("%18s: %li ms\n", "frame time", frame_time);
|
|
}
|
|
|
|
j = 0;
|
|
for (i = 0; i < TC_MAX_AUD_TRACKS; i++) {
|
|
if (ipipe->probe_info->track[i].format != 0
|
|
&& ipipe->probe_info->track[i].chan > 0) {
|
|
double pts_diff = 0.0;
|
|
int hint_frames = 0, hint_ms = 0;
|
|
if (ipipe->probe_info->pts_start > 0
|
|
&& ipipe->probe_info->track[i].pts_start > 0
|
|
&& ipipe->probe_info->fps != 0) {
|
|
pts_diff = ipipe->probe_info->pts_start - ipipe->probe_info->track[i].pts_start;
|
|
hint_frames = (int)(pts_diff * ipipe->probe_info->fps);
|
|
hint_ms = (int)((pts_diff - hint_frames / ipipe->probe_info->fps) * 1000);
|
|
}
|
|
|
|
printf("* audio track #%i:\n", j);
|
|
/* XXX */
|
|
printf("%18s: %i\n", "track id",
|
|
ipipe->probe_info->track[i].tid);
|
|
/* XXX */
|
|
printf("%18s: 0x%x\n", "format",
|
|
ipipe->probe_info->track[i].format);
|
|
printf("%18s: %i\n", "channels",
|
|
ipipe->probe_info->track[i].chan);
|
|
printf("%18s: %i Hz\n", "sample rate",
|
|
ipipe->probe_info->track[i].samplerate);
|
|
printf("%18s: %i\n", "bits for sample",
|
|
ipipe->probe_info->track[i].bits);
|
|
|
|
printf("%18s: %i kbps\n", "bitrate",
|
|
ipipe->probe_info->track[i].bitrate);
|
|
printf("%18s: %.4f\n", "starting PTS",
|
|
ipipe->probe_info->track[i].pts_start);
|
|
printf("%18s: %i frames/%i ms\n", "A/V sync hint",
|
|
hint_frames, hint_ms);
|
|
/* have subtitles here? */
|
|
printf("%18s: %s\n", "subtitles",
|
|
(ipipe->probe_info->track[i].attribute & PACKAGE_SUBTITLE) ?"yes" :"no");
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
|
|
/* ------------------------------------------------------------
|
|
*
|
|
* print a usage/version message
|
|
*
|
|
* ------------------------------------------------------------*/
|
|
|
|
void version(void)
|
|
{
|
|
/* print id string to stderr */
|
|
printf("%s (%s v%s) (C) 2001-2010 Thomas Oestreich, Transcode team\n",
|
|
EXE, PACKAGE, VERSION);
|
|
}
|
|
|
|
|
|
static void usage(int status)
|
|
{
|
|
version();
|
|
|
|
printf("Usage: %s [options] [-]\n", EXE);
|
|
printf(" -i name input file/directory/device/host"
|
|
" name [stdin]\n");
|
|
printf(" -B binary output to stdout"
|
|
" (used by transcode) [off]\n");
|
|
printf(" -M use EXPERIMENTAL mplayer probe [off]\n");
|
|
printf(" -R raw mode: produce machine-friendly"
|
|
" output [off]\n");
|
|
printf(" -X new extended output mode [off]\n");
|
|
printf(" -H n probe n MB of stream [1]\n");
|
|
printf(" -s n skip first n bytes of stream [0]\n");
|
|
printf(" -T title probe for DVD title [off]\n");
|
|
printf(" -b bitrate audio encoder bitrate kBits/s [%d]\n",
|
|
ABITRATE);
|
|
printf(" -f seekfile seek/index file [off]\n");
|
|
printf(" -d verbosity verbosity mode [1]\n");
|
|
printf(" -v print version\n");
|
|
|
|
exit(status);
|
|
}
|
|
|
|
/* ------------------------------------------------------------
|
|
* universal probing code frontend
|
|
* ------------------------------------------------------------*/
|
|
|
|
/* very basic option sanity check */
|
|
#define VALIDATE_OPTION \
|
|
if (optarg[0]=='-') { \
|
|
usage(EXIT_FAILURE); \
|
|
}
|
|
|
|
#define VALIDATE_PARAM(parm, opt, min) \
|
|
if ((parm) < (min)) { \
|
|
tc_log_error(EXE, "invalid parameter for option %s", (opt)); \
|
|
exit(16); \
|
|
}
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
info_t ipipe;
|
|
InfoDumpFn output_handler = dump_info_old;
|
|
/* standard old style output */
|
|
|
|
int mplayer_probe = TC_FALSE;
|
|
int ch, skip = 0, want_dvd = 0, ret;
|
|
const char *name = NULL;
|
|
|
|
/* proper initialization */
|
|
memset(&ipipe, 0, sizeof(info_t));
|
|
ipipe.stype = TC_STYPE_UNKNOWN;
|
|
ipipe.seek_allowed = 0;
|
|
ipipe.factor = 1;
|
|
ipipe.dvd_title = 1;
|
|
|
|
libtc_init(&argc, &argv);
|
|
|
|
while ((ch = getopt(argc, argv, "i:vBMRXd:T:f:b:s:H:?h")) != -1) {
|
|
switch (ch) {
|
|
case 'b':
|
|
VALIDATE_OPTION;
|
|
bitrate = atoi(optarg);
|
|
VALIDATE_PARAM(bitrate, "-b", 0);
|
|
break;
|
|
case 'i':
|
|
VALIDATE_OPTION;
|
|
name = optarg;
|
|
break;
|
|
case 'f':
|
|
VALIDATE_OPTION;
|
|
ipipe.nav_seek_file = optarg;
|
|
break;
|
|
case 'd':
|
|
VALIDATE_OPTION;
|
|
verbose = atoi(optarg);
|
|
break;
|
|
case 's':
|
|
VALIDATE_OPTION;
|
|
skip = atoi(optarg);
|
|
break;
|
|
case 'H':
|
|
VALIDATE_OPTION;
|
|
ipipe.factor = atoi(optarg); /* how much data for probing? */
|
|
VALIDATE_PARAM(bitrate, "-H", 0);
|
|
break;
|
|
case 'B':
|
|
output_handler = dump_info_binary;
|
|
binary_dump = 1; /* XXX: compatibility with probe_mov -- FR */
|
|
break;
|
|
case 'M':
|
|
mplayer_probe = TC_TRUE;
|
|
break;
|
|
case 'R':
|
|
output_handler = dump_info_raw;
|
|
break;
|
|
case 'X':
|
|
output_handler = dump_info_new;
|
|
break;
|
|
case 'T':
|
|
VALIDATE_OPTION;
|
|
ipipe.dvd_title = atoi(optarg);
|
|
want_dvd = 1;
|
|
break;
|
|
case 'v':
|
|
version();
|
|
exit(0);
|
|
break;
|
|
case 'h':
|
|
usage(EXIT_SUCCESS);
|
|
break;
|
|
default:
|
|
usage(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* need at least a file name */
|
|
if (argc == 1) {
|
|
usage(EXIT_FAILURE);
|
|
}
|
|
|
|
if (optind < argc) {
|
|
if (strcmp(argv[optind],"-") != 0) {
|
|
usage(EXIT_FAILURE);
|
|
}
|
|
ipipe.stype = TC_STYPE_STDIN;
|
|
}
|
|
|
|
/* assume defaults */
|
|
if (name == NULL) {
|
|
ipipe.stype = TC_STYPE_STDIN;
|
|
} else {
|
|
if (tc_x11source_is_display_name(name)) {
|
|
ipipe.stype = TC_STYPE_X11;
|
|
}
|
|
}
|
|
ipipe.verbose = verbose;
|
|
ipipe.fd_out = STDOUT_FILENO;
|
|
ipipe.codec = TC_CODEC_UNKNOWN;
|
|
ipipe.name = name;
|
|
|
|
/* do not try to mess with the stream */
|
|
if (ipipe.stype == TC_STYPE_STDIN) {
|
|
ipipe.fd_in = STDIN_FILENO;
|
|
ipipe.magic = streaminfo(ipipe.fd_in);
|
|
} else if (ipipe.stype == TC_STYPE_X11) {
|
|
ipipe.fd_in = STDIN_FILENO; /* XXX */
|
|
ipipe.magic = TC_MAGIC_X11;
|
|
} else {
|
|
ret = info_setup(&ipipe, skip, mplayer_probe, want_dvd);
|
|
if (ret != TC_IMPORT_OK) {
|
|
/* already logged out why */
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------
|
|
* codec specific section
|
|
* note: user provided values overwrite autodetection!
|
|
* ------------------------------------------------------------*/
|
|
|
|
probe_stream(&ipipe);
|
|
|
|
if (ipipe.error == 0) {
|
|
output_handler(&ipipe);
|
|
} else if (ipipe.error == 1) {
|
|
if (verbose) {
|
|
tc_log_error(EXE, "failed to probe source");
|
|
}
|
|
} else if (ipipe.error == 2) {
|
|
if (verbose) {
|
|
tc_log_error(EXE, "filetype/codec not yet supported by '%s'",
|
|
PACKAGE);
|
|
}
|
|
}
|
|
|
|
info_teardown(&ipipe);
|
|
return ipipe.error;
|
|
}
|
|
|
|
#include "libtc/static_xio.h"
|
|
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-file-style: "stroustrup"
|
|
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vim: expandtab shiftwidth=4:
|
|
*/
|