mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-25 06:35:57 -05:00
Add Tim's ogg and flac patches, plus convert the existing ogg metainfo reading
stuff to use vorbisfile.
This commit is contained in:
parent
ecaae83ce5
commit
9afb28a4ab
26
configure.in
26
configure.in
@ -49,12 +49,16 @@ AC_ARG_ENABLE(howl,[ --enable-howl Use howl 0.9.2 or later],
|
||||
|
||||
AC_ARG_ENABLE(oggvorbis,[ --enable-oggvorbis Enable Ogg/Vorbis support],
|
||||
use_oggvorbis=true;
|
||||
# LDFLAGS="${LDFLAGS} -logg -lvorbis";
|
||||
CPPFLAGS="${CPPFLAGS} -DOGGVORBIS")
|
||||
|
||||
AC_ARG_ENABLE(flac,[ --enable-flac Enable FLAC support],
|
||||
use_flac=true;
|
||||
CPPFLAGS="${CPPFLAGS} -DFLAC")
|
||||
|
||||
AM_CONDITIONAL(COND_REND_HOWL, test x$rend_howl = xtrue)
|
||||
AM_CONDITIONAL(COND_REND_POSIX, test x$rend_posix = xtrue)
|
||||
AM_CONDITIONAL(COND_OGGVORBIS, test x$use_oggvorbis = xtrue)
|
||||
AM_CONDITIONAL(COND_FLAC, test x$use_flac = xtrue)
|
||||
|
||||
AM_CONDITIONAL(COND_NEED_STRCASESTR,false)
|
||||
AM_CONDITIONAL(COND_NEED_STRSEP,false)
|
||||
@ -184,16 +188,32 @@ if test x$use_oggvorbis = xtrue; then
|
||||
AC_CHECK_HEADERS(vorbis/codec.h,, [
|
||||
AC_MSG_ERROR([vorbis/codec.h not found... Must have libvorbis installed for Ogg/Vorbis support])])
|
||||
AC_CHECK_LIB(vorbis,vorbis_info_init,echo "Have vorbis",echo "Must have libvorbis for Ogg/Vorbis support";exit)
|
||||
AC_CHECK_LIB(vorbisfile,ov_open,echo "Have vorbisfile",echo "Must have libvorbisfile for Ogg/Vorbis support";exit)
|
||||
|
||||
if test x"$STATIC_LIBS" != x"no"; then
|
||||
LDFLAGS="${LDFLAGS} ${STATIC_LIBS}/libvorbis.a"
|
||||
LDFLAGS="${LDFLAGS} ${STATIC_LIBS}/libvorbis.a ${STATIC_LIBS}/libvorbisfile.a"
|
||||
echo "Adding static libvorbis"
|
||||
else
|
||||
LDFLAGS="${LDFLAGS} -lvorbis"
|
||||
LDFLAGS="${LDFLAGS} -lvorbis -lvorbisfile"
|
||||
echo "Adding dynamic libvorbis"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo USE_FLAC is $use_flac
|
||||
|
||||
if test x$use_flac = xtrue; then
|
||||
AC_CHECK_HEADERS(FLAC/metadata.h,, [
|
||||
AC_MSG_ERROR([FLAC/metadata.h not found... Must have libFLAC installed for FLAC support])])
|
||||
AC_CHECK_LIB(FLAC,FLAC__metadata_chain_read,echo "Have FLAC",echo "Must have libFLAC for FLAC support";exit)
|
||||
|
||||
if test x"$STATIC_LIBS" != x"no"; then
|
||||
LDFLAGS="${LDFLAGS} ${STATIC_LIBS}/libFLAC.a"
|
||||
echo "Adding static libFLAC"
|
||||
else
|
||||
LDFLAGS="${LDFLAGS} -lFLAC"
|
||||
echo "Adding dynamic libFLAC"
|
||||
fi
|
||||
fi
|
||||
|
||||
AC_REPLACE_FUNCS(strcasestr)
|
||||
AC_REPLACE_FUNCS(strsep)
|
||||
|
@ -141,13 +141,15 @@ ssc_extensions .ogg
|
||||
#
|
||||
# Program that is used in server side format conversion.
|
||||
# Program must accept following command line syntax:
|
||||
# ssc_prog filename offset
|
||||
# ssc_prog filename offset length ...
|
||||
# Parameter filename is the real name of the file that is
|
||||
# to be converted and streamed, offset is number of bytes
|
||||
# that are skipped from the beginning of the _output_ file
|
||||
# before streaming is started. The resulting wav file (or
|
||||
# rest of the file after initial seek) is written to the
|
||||
# standard output by the ssc_prog program. This is typically
|
||||
# before streaming is started, length is length of the song
|
||||
# in seconds (or zero). All other possible arguments must
|
||||
# be ignored. The resulting wav file (or the rest of
|
||||
# the file after initial seek) is written to the standard
|
||||
# output by the ssc_prog program. This is typically
|
||||
# a script that is a front end for different conversion tools
|
||||
# handling different formats.
|
||||
#
|
||||
|
@ -22,6 +22,10 @@ if COND_OGGVORBIS
|
||||
OGGVORBISSRC=ogg.c
|
||||
endif
|
||||
|
||||
if COND_FLAC
|
||||
FLACSRC=flac.c
|
||||
endif
|
||||
|
||||
mt_daapd_SOURCES = main.c daapd.h rend.h uici.c uici.h webserver.c \
|
||||
webserver.h configfile.c configfile.h err.c err.h restart.c restart.h \
|
||||
daap-proto.c daap-proto.h daap.c daap.h db-gdbm.c db-memory.h \
|
||||
@ -29,10 +33,10 @@ mt_daapd_SOURCES = main.c daapd.h rend.h uici.c uici.h webserver.c \
|
||||
rend-unix.h lexer.l parser.y strcasestr.c strcasestr.h strsep.c \
|
||||
redblack.c redblack.h dynamic-art.c dynamic-art.h query.c query.h \
|
||||
xml-rpc.h xml-rpc.c ssc.c ssc.h \
|
||||
$(PRENDSRC) $(ORENDSRC) $(HRENDSRC) $(OGGVORBISSRC)
|
||||
$(PRENDSRC) $(ORENDSRC) $(HRENDSRC) $(OGGVORBISSRC) $(FLACSRC)
|
||||
|
||||
EXTRA_DIST = mDNS.c mDNSClientAPI.h mDNSDebug.h mDNSPosix.c \
|
||||
mDNSUNP.c mDNSPlatformFunctions.h mDNSPosix.h mDNSUNP.h \
|
||||
rend-howl.c rend-posix.c rend-osx.c db-memory.c \
|
||||
db-gdbm.c strcasestr.h redblack.c redblack.h db-memory.c \
|
||||
parser.h ogg.c
|
||||
parser.h ogg.c flac.c
|
||||
|
@ -1263,6 +1263,10 @@ void config_emit_flags(WS_CONNINFO *pwsc, void *value, char *arg) {
|
||||
ws_writefd(pwsc,"%s ","--enable-oggvorbis");
|
||||
#endif
|
||||
|
||||
#ifdef FLAC
|
||||
ws_writefd(pwsc,"%s ","--enable-flac");
|
||||
#endif
|
||||
|
||||
#ifdef WITH_GDBM
|
||||
ws_writefd(pwsc,"%s ","--with-gdbm");
|
||||
#endif
|
||||
|
@ -392,7 +392,9 @@ void daap_handler(WS_CONNINFO *pwsc) {
|
||||
// The file should be converted in the server side.
|
||||
DPRINTF(E_WARN,L_WS,"Thread %d: Autoconvert file %s for client\n",
|
||||
pwsc->threadno,real_path);
|
||||
file_ptr=server_side_convert_open(real_path,offset);
|
||||
file_ptr = server_side_convert_open(real_path,
|
||||
offset,
|
||||
pmp3->song_length);
|
||||
if (file_ptr) {
|
||||
file_fd = fileno(file_ptr);
|
||||
} else {
|
||||
|
@ -281,6 +281,7 @@ static int scan_get_nultags(char *file, MP3FILE *pmp3) { return 0; };
|
||||
static int scan_get_fileinfo(char *file, MP3FILE *pmp3);
|
||||
static int scan_get_mp3fileinfo(char *file, MP3FILE *pmp3);
|
||||
static int scan_get_aacfileinfo(char *file, MP3FILE *pmp3);
|
||||
static int scan_get_wavfileinfo(char *file, MP3FILE *pmp3);
|
||||
static int scan_get_nulfileinfo(char *file, MP3FILE *pmp3) { return 0; };
|
||||
static int scan_get_urlfileinfo(char *file, MP3FILE *pmp3);
|
||||
|
||||
@ -295,6 +296,11 @@ static time_t mac_to_unix_time(int t);
|
||||
extern int scan_get_oggfileinfo(char *filename, MP3FILE *pmp3);
|
||||
#endif
|
||||
|
||||
#ifdef FLAC
|
||||
extern int scan_get_flacfileinfo(char *filename, MP3FILE *pmp3);
|
||||
extern int scan_get_flactags(char *filename, MP3FILE *pmp3);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Typedefs
|
||||
*/
|
||||
@ -311,9 +317,14 @@ static taghandler taghandlers[] = {
|
||||
{ "m4a", scan_get_aactags, scan_get_aacfileinfo },
|
||||
{ "m4p", scan_get_aactags, scan_get_aacfileinfo },
|
||||
{ "mp3", scan_get_mp3tags, scan_get_mp3fileinfo },
|
||||
{ "wav", scan_get_nultags, scan_get_wavfileinfo },
|
||||
{ "url", scan_get_nultags, scan_get_urlfileinfo },
|
||||
#ifdef OGGVORBIS
|
||||
{ "ogg", scan_get_nultags, scan_get_oggfileinfo },
|
||||
#endif
|
||||
#ifdef FLAC
|
||||
{ "flac", scan_get_flactags, scan_get_flacfileinfo },
|
||||
{ "fla", scan_get_flactags, scan_get_flacfileinfo },
|
||||
#endif
|
||||
{ NULL, 0 }
|
||||
};
|
||||
@ -1231,6 +1242,84 @@ int scan_get_aacfileinfo(char *file, MP3FILE *pmp3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define GET_WAV_INT32(p) ((((unsigned long)((p)[3])) << 24) | \
|
||||
(((unsigned long)((p)[2])) << 16) | \
|
||||
(((unsigned long)((p)[1])) << 8) | \
|
||||
(((unsigned long)((p)[0]))))
|
||||
|
||||
#define GET_WAV_INT16(p) ((((unsigned long)((p)[1])) << 8) | \
|
||||
(((unsigned long)((p)[0]))))
|
||||
|
||||
/*
|
||||
* scan_get_wavfileinfo
|
||||
*
|
||||
* Get info from the actual wav headers
|
||||
*/
|
||||
int scan_get_wavfileinfo(char *file, MP3FILE *pmp3) {
|
||||
FILE *infile;
|
||||
size_t rl;
|
||||
unsigned char hdr[44];
|
||||
unsigned long chunk_data_length;
|
||||
unsigned long format_data_length;
|
||||
unsigned long compression_code;
|
||||
unsigned long channel_count;
|
||||
unsigned long sample_rate;
|
||||
unsigned long sample_bit_length;
|
||||
unsigned long bit_rate;
|
||||
unsigned long data_length;
|
||||
unsigned long sec, ms;
|
||||
|
||||
DPRINTF(E_DBG,L_SCAN,"Getting WAV file info\n");
|
||||
|
||||
if(!(infile=fopen(file,"rb"))) {
|
||||
DPRINTF(E_WARN,L_SCAN,"Could not open %s for reading\n",file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(infile,0,SEEK_END);
|
||||
pmp3->file_size = ftell(infile);
|
||||
fseek(infile,0,SEEK_SET);
|
||||
|
||||
rl = fread(hdr, 1, 44, infile);
|
||||
fclose(infile);
|
||||
if (rl != 44) {
|
||||
DPRINTF(E_WARN,L_SCAN,"Could not read wav header from %s\n",file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strncmp(hdr + 0, "RIFF", 4) ||
|
||||
strncmp(hdr + 8, "WAVE", 4) ||
|
||||
strncmp(hdr + 12, "fmt ", 4) ||
|
||||
strncmp(hdr + 36, "data", 4)) {
|
||||
DPRINTF(E_WARN,L_SCAN,"Invalid wav header in %s\n",file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
chunk_data_length = GET_WAV_INT32(hdr + 4);
|
||||
format_data_length = GET_WAV_INT32(hdr + 16);
|
||||
compression_code = GET_WAV_INT16(hdr + 20);
|
||||
channel_count = GET_WAV_INT16(hdr + 22);
|
||||
sample_rate = GET_WAV_INT32(hdr + 24);
|
||||
sample_bit_length = GET_WAV_INT16(hdr + 34);
|
||||
data_length = GET_WAV_INT32(hdr + 40);
|
||||
|
||||
if ((format_data_length != 16) ||
|
||||
(compression_code != 1) ||
|
||||
(channel_count < 1)) {
|
||||
DPRINTF(E_WARN,L_SCAN,"Invalid wav header in %s\n",file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bit_rate = sample_rate * channel_count * ((sample_bit_length + 7) / 8) * 8;
|
||||
pmp3->bitrate = bit_rate / 1000;
|
||||
pmp3->samplerate = sample_rate;
|
||||
sec = data_length / (bit_rate / 8);
|
||||
ms = ((data_length % (bit_rate / 8)) * 1000) / (bit_rate / 8);
|
||||
pmp3->song_length = (sec * 1000) + ms;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode an mp3 frame header. Determine layer, bitrate,
|
||||
|
815
src/ogg.c
815
src/ogg.c
@ -2,12 +2,6 @@
|
||||
* $Id$
|
||||
* Ogg parsing routines.
|
||||
*
|
||||
* This file has been modified from the 'ogginfo' code in the vorbistools
|
||||
* distribution. The original copyright appears below:
|
||||
*
|
||||
* Ogginfo
|
||||
*
|
||||
* A tool to describe ogg file contents and metadata.
|
||||
*
|
||||
* Copyright 2002 Michael Smith <msmith@xiph.org>
|
||||
* Licensed under the GNU GPL, distributed with this program.
|
||||
@ -17,776 +11,77 @@
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <ogg/ogg.h>
|
||||
#include <vorbis/codec.h>
|
||||
/*
|
||||
#include <locale.h>
|
||||
#include "utf8.h"
|
||||
#include "i18n.h"
|
||||
*/
|
||||
#include <vorbis/vorbisfile.h>
|
||||
#include "err.h"
|
||||
#include "mp3-scanner.h"
|
||||
|
||||
#define CHUNK 4500
|
||||
|
||||
struct vorbis_release {
|
||||
char *vendor_string;
|
||||
char *desc;
|
||||
} releases[] = {
|
||||
{"Xiphophorus libVorbis I 20000508", "1.0 beta 1 or beta 2"},
|
||||
{"Xiphophorus libVorbis I 20001031", "1.0 beta 3"},
|
||||
{"Xiphophorus libVorbis I 20010225", "1.0 beta 4"},
|
||||
{"Xiphophorus libVorbis I 20010615", "1.0 rc1"},
|
||||
{"Xiphophorus libVorbis I 20010813", "1.0 rc2"},
|
||||
{"Xiphophorus libVorbis I 20011217", "1.0 rc3"},
|
||||
{"Xiphophorus libVorbis I 20011231", "1.0 rc3"},
|
||||
{"Xiph.Org libVorbis I 20020717", "1.0"},
|
||||
{"Xiph.Org libVorbis I 20030909", "1.0.1"},
|
||||
{NULL, NULL},
|
||||
};
|
||||
|
||||
|
||||
/* TODO:
|
||||
*
|
||||
* - detect violations of muxing constraints
|
||||
* - detect granulepos 'gaps' (possibly vorbis-specific). (seperate from
|
||||
* serial-number gaps)
|
||||
*/
|
||||
|
||||
typedef struct _stream_processor {
|
||||
void (*process_page)(struct _stream_processor *, ogg_page *, MP3FILE *);
|
||||
void (*process_end)(struct _stream_processor *, MP3FILE *);
|
||||
int isillegal;
|
||||
int constraint_violated;
|
||||
int shownillegal;
|
||||
int isnew;
|
||||
long seqno;
|
||||
int lostseq;
|
||||
|
||||
int start;
|
||||
int end;
|
||||
|
||||
int num;
|
||||
char *type;
|
||||
|
||||
ogg_uint32_t serial; /* must be 32 bit unsigned */
|
||||
ogg_stream_state os;
|
||||
void *data;
|
||||
} stream_processor;
|
||||
|
||||
typedef struct {
|
||||
stream_processor *streams;
|
||||
int allocated;
|
||||
int used;
|
||||
|
||||
int in_headers;
|
||||
} stream_set;
|
||||
|
||||
typedef struct {
|
||||
vorbis_info vi;
|
||||
vorbis_comment vc;
|
||||
|
||||
ogg_int64_t bytes;
|
||||
ogg_int64_t lastgranulepos;
|
||||
ogg_int64_t firstgranulepos;
|
||||
|
||||
int doneheaders;
|
||||
} misc_vorbis_info;
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
static void vorbis_process(stream_processor *stream, ogg_page *page,
|
||||
MP3FILE *pmp3)
|
||||
{
|
||||
ogg_packet packet;
|
||||
misc_vorbis_info *inf = stream->data;
|
||||
int i, header=0;
|
||||
int k;
|
||||
|
||||
ogg_stream_pagein(&stream->os, page);
|
||||
if(inf->doneheaders < 3)
|
||||
header = 1;
|
||||
|
||||
while(ogg_stream_packetout(&stream->os, &packet) > 0) {
|
||||
if(inf->doneheaders < 3) {
|
||||
if(vorbis_synthesis_headerin(&inf->vi, &inf->vc, &packet) < 0) {
|
||||
DPRINTF(E_WARN, L_SCAN, "Could not decode vorbis header "
|
||||
"packet - invalid vorbis stream (%d)\n", stream->num);
|
||||
continue;
|
||||
}
|
||||
inf->doneheaders++;
|
||||
if(inf->doneheaders == 3) {
|
||||
if(ogg_page_granulepos(page) != 0 || ogg_stream_packetpeek(&stream->os, NULL) == 1)
|
||||
DPRINTF(E_WARN, L_SCAN, "Vorbis stream %d does not have headers "
|
||||
"correctly framed. Terminal header page contains "
|
||||
"additional packets or has non-zero granulepos\n",
|
||||
stream->num);
|
||||
DPRINTF(E_DBG, L_SCAN, "Vorbis headers parsed for stream %d, "
|
||||
"information follows...\n", stream->num);
|
||||
/*
|
||||
DPRINTF(E_INF, L_SCAN, "Version: %d\n", inf->vi.version);
|
||||
k = 0;
|
||||
while(releases[k].vendor_string) {
|
||||
if(!strcmp(inf->vc.vendor, releases[k].vendor_string)) {
|
||||
//PENDING:
|
||||
DPRINTF(E_INF, L_SCAN, "Vendor: %s (%s)\n",
|
||||
inf->vc.vendor, releases[k].desc);
|
||||
break;
|
||||
}
|
||||
k++;
|
||||
}
|
||||
|
||||
if(!releases[k].vendor_string)
|
||||
DPRINTF(E_INF, L_SCAN, "Vendor: %s\n", inf->vc.vendor);*/
|
||||
|
||||
DPRINTF(E_DBG, L_SCAN, "Channels: %d\n", inf->vi.channels);
|
||||
DPRINTF(E_DBG, L_SCAN, "Rate: %ld\n\n", inf->vi.rate);
|
||||
|
||||
pmp3->samplerate = inf->vi.rate;
|
||||
|
||||
if(inf->vi.bitrate_nominal > 0) {
|
||||
DPRINTF(E_DBG, L_SCAN, "Nominal bitrate: %f kb/s\n",
|
||||
(double)inf->vi.bitrate_nominal / 1000.0);
|
||||
pmp3->bitrate = inf->vi.bitrate_nominal / 1000;
|
||||
}
|
||||
else {
|
||||
int upper_rate, lower_rate;
|
||||
|
||||
DPRINTF(E_DBG, L_SCAN, "Nominal bitrate not set\n");
|
||||
|
||||
/* Average the lower and upper bitrates if set */
|
||||
|
||||
upper_rate = 0;
|
||||
lower_rate = 0;
|
||||
|
||||
if(inf->vi.bitrate_upper > 0) {
|
||||
DPRINTF(E_DBG, L_SCAN, "Upper bitrate: %f kb/s\n",
|
||||
(double)inf->vi.bitrate_upper / 1000.0);
|
||||
upper_rate = inf->vi.bitrate_upper;
|
||||
} else {
|
||||
DPRINTF(E_DBG, L_SCAN, "Upper bitrate not set\n");
|
||||
}
|
||||
|
||||
if(inf->vi.bitrate_lower > 0) {
|
||||
DPRINTF(E_DBG, L_SCAN,"Lower bitrate: %f kb/s\n",
|
||||
(double)inf->vi.bitrate_lower / 1000.0);
|
||||
lower_rate = inf->vi.bitrate_lower;;
|
||||
} else {
|
||||
DPRINTF(E_DBG, L_SCAN, "Lower bitrate not set\n");
|
||||
}
|
||||
|
||||
if (upper_rate && lower_rate) {
|
||||
pmp3->bitrate = (upper_rate + lower_rate) / 2;
|
||||
} else {
|
||||
pmp3->bitrate = upper_rate + lower_rate;
|
||||
}
|
||||
}
|
||||
|
||||
if(inf->vc.comments > 0)
|
||||
DPRINTF(E_DBG, L_SCAN,
|
||||
"User comments section follows...\n");
|
||||
|
||||
for(i=0; i < inf->vc.comments; i++) {
|
||||
char *sep = strchr(inf->vc.user_comments[i], '=');
|
||||
char *decoded;
|
||||
int j;
|
||||
int broken = 0;
|
||||
unsigned char *val;
|
||||
int bytes;
|
||||
int remaining;
|
||||
|
||||
if(sep == NULL) {
|
||||
DPRINTF(E_WARN, L_SCAN,
|
||||
"Comment %d in stream %d is invalidly "
|
||||
"formatted, does not contain '=': \"%s\"\n",
|
||||
i, stream->num, inf->vc.user_comments[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
for(j=0; j < sep-inf->vc.user_comments[i]; j++) {
|
||||
if(inf->vc.user_comments[i][j] < 0x20 ||
|
||||
inf->vc.user_comments[i][j] > 0x7D) {
|
||||
DPRINTF(E_WARN, L_SCAN,
|
||||
"Warning: Invalid comment fieldname in "
|
||||
"comment %d (stream %d): \"%s\"\n",
|
||||
i, stream->num, inf->vc.user_comments[i]);
|
||||
broken = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(broken)
|
||||
continue;
|
||||
|
||||
val = inf->vc.user_comments[i];
|
||||
|
||||
j = sep-inf->vc.user_comments[i]+1;
|
||||
while(j < inf->vc.comment_lengths[i])
|
||||
{
|
||||
remaining = inf->vc.comment_lengths[i] - 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 {
|
||||
DPRINTF(E_WARN, L_SCAN,
|
||||
"Illegal UTF-8 sequence in "
|
||||
"comment %d (stream %d): length "
|
||||
"marker wrong\n",
|
||||
i, stream->num);
|
||||
broken = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
DPRINTF(E_WARN, L_SCAN,
|
||||
"Illegal UTF-8 sequence in comment "
|
||||
"%d (stream %d): length marker wrong\n",
|
||||
i, stream->num);
|
||||
broken = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if(bytes > remaining) {
|
||||
DPRINTF(E_WARN, L_SCAN,
|
||||
"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) {
|
||||
DPRINTF(E_WARN, L_SCAN,
|
||||
"Illegal UTF-8 sequence in comment "
|
||||
"%d (stream %d): invalid sequence\n",
|
||||
i, stream->num);
|
||||
broken = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
j += bytes;
|
||||
}
|
||||
|
||||
if(!broken) {
|
||||
/* if(utf8_decode(sep+1, &decoded) < 0) {
|
||||
DPRINTF(E_WARN, L_SCAN,
|
||||
"Failure in utf8 decoder. This "
|
||||
"should be impossible\n");
|
||||
continue;
|
||||
}*/
|
||||
|
||||
DPRINTF(E_DBG, L_SCAN,
|
||||
"\t%s\n", inf->vc.user_comments[i]);
|
||||
|
||||
if (!strncasecmp(inf->vc.user_comments[i],"TITLE",5)) {
|
||||
pmp3->title = strdup(sep + 1);
|
||||
DPRINTF(E_DBG, L_SCAN, "Mapping %s to title.\n",
|
||||
sep + 1);
|
||||
} else if (!strncasecmp(inf->vc.user_comments[i], "ARTIST", 6)) {
|
||||
pmp3->artist = strdup(sep + 1);
|
||||
DPRINTF(E_DBG, L_SCAN, "Mapping %s to artist.\n",
|
||||
sep + 1);
|
||||
} else if (!strncasecmp(inf->vc.user_comments[i], "ALBUM", 5)) {
|
||||
pmp3->album = strdup(sep + 1);
|
||||
DPRINTF(E_DBG, L_SCAN, "Mapping %s to album.\n",
|
||||
sep + 1);
|
||||
} else if (!strncasecmp(inf->vc.user_comments[i],
|
||||
"TRACKNUMBER", 11)) {
|
||||
pmp3->track = atoi(sep + 1);
|
||||
DPRINTF(E_INF, L_SCAN, "Mapping %s to track.\n",
|
||||
sep + 1);
|
||||
} else if (!strncasecmp(inf->vc.user_comments[i], "GENRE", 5)) {
|
||||
pmp3->genre = strdup(sep + 1);
|
||||
DPRINTF(E_INF, L_SCAN, "Mapping %s to genre.\n",
|
||||
sep + 1);
|
||||
} else if (!strncasecmp(inf->vc.user_comments[i], "DATE", 4)) {
|
||||
//PENDING: Should only parse first 4 characters
|
||||
pmp3->year = atoi(sep + 1);
|
||||
DPRINTF(E_INF, L_SCAN, "Mapping %s to year.\n",
|
||||
sep + 1);
|
||||
} else if (!strncasecmp(inf->vc.user_comments[i], "COMMENT", 7)) {
|
||||
pmp3->comment = strdup(sep + 1);
|
||||
DPRINTF(E_INF, L_SCAN, "Mapping %s to comment.\n",
|
||||
sep + 1);
|
||||
}
|
||||
|
||||
*sep = 0;
|
||||
/* free(decoded);*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!header) {
|
||||
ogg_int64_t gp = ogg_page_granulepos(page);
|
||||
if(gp > 0) {
|
||||
if(gp < inf->lastgranulepos)
|
||||
#ifdef _WIN32
|
||||
DPRINTF(E_WARN, L_SCAN, "granulepos in stream %d decreases from %I64d to %I64d",
|
||||
stream->num, inf->lastgranulepos, gp);
|
||||
#else
|
||||
DPRINTF(E_WARN, L_SCAN, "granulepos in stream %d decreases from %lld to %lld",
|
||||
stream->num, inf->lastgranulepos, gp);
|
||||
#endif
|
||||
inf->lastgranulepos = gp;
|
||||
}
|
||||
else {
|
||||
DPRINTF(E_WARN, L_SCAN, "Negative granulepos on vorbis stream outside of headers. This file was created by a buggy encoder\n");
|
||||
}
|
||||
if(inf->firstgranulepos < 0) { /* Not set yet */
|
||||
}
|
||||
inf->bytes += page->header_len + page->body_len;
|
||||
}
|
||||
}
|
||||
|
||||
static void vorbis_end(stream_processor *stream, MP3FILE *pmp3)
|
||||
{
|
||||
misc_vorbis_info *inf = stream->data;
|
||||
long minutes, seconds;
|
||||
double bitrate, time;
|
||||
|
||||
/* This should be lastgranulepos - startgranulepos, or something like that*/
|
||||
time = (double)inf->lastgranulepos / inf->vi.rate;
|
||||
bitrate = inf->bytes*8 / time / 1000.0;
|
||||
|
||||
if (pmp3 != NULL) {
|
||||
if (pmp3->bitrate <= 0) {
|
||||
pmp3->bitrate = bitrate;
|
||||
}
|
||||
pmp3->song_length = time * 1000;
|
||||
pmp3->file_size = inf->bytes;
|
||||
}
|
||||
|
||||
minutes = (long)time / 60;
|
||||
seconds = (long)time - minutes*60;
|
||||
|
||||
#ifdef _WIN32
|
||||
DPRINTF(E_DBG, L_SCAN, "Vorbis stream %d:\n"
|
||||
"\tTotal data length: %I64d bytes\n"
|
||||
"\tPlayback length: %ldm:%02lds\n"
|
||||
"\tAverage bitrate: %f kbps\n",
|
||||
stream->num,inf->bytes, minutes, seconds, bitrate);
|
||||
#else
|
||||
DPRINTF(E_DBG, L_SCAN, "Vorbis stream %d:\n"
|
||||
"\tTotal data length: %lld bytes\n"
|
||||
"\tPlayback length: %ldm:%02lds\n"
|
||||
"\tAverage bitrate: %f kbps\n",
|
||||
stream->num,inf->bytes, minutes, seconds, bitrate);
|
||||
#endif
|
||||
|
||||
vorbis_comment_clear(&inf->vc);
|
||||
vorbis_info_clear(&inf->vi);
|
||||
|
||||
free(stream->data);
|
||||
}
|
||||
|
||||
static void process_null(stream_processor *stream, ogg_page *page, MP3FILE *pmp)
|
||||
{
|
||||
/* This is for invalid streams. */
|
||||
}
|
||||
|
||||
static void process_other(stream_processor *stream, ogg_page *page, MP3FILE *pmp)
|
||||
{
|
||||
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) {
|
||||
DPRINTF(E_WARN, L_SCAN, "Warning: EOS not set on stream %d\n",
|
||||
set->streams[i].num);
|
||||
//PENDING:
|
||||
if(set->streams[i].process_end)
|
||||
set->streams[i].process_end(&set->streams[i], NULL);
|
||||
}
|
||||
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 void vorbis_start(stream_processor *stream)
|
||||
{
|
||||
misc_vorbis_info *info;
|
||||
|
||||
stream->type = "vorbis";
|
||||
stream->process_page = vorbis_process;
|
||||
stream->process_end = vorbis_end;
|
||||
|
||||
stream->data = calloc(1, sizeof(misc_vorbis_info));
|
||||
|
||||
info = stream->data;
|
||||
|
||||
vorbis_comment_init(&info->vc);
|
||||
vorbis_info_init(&info->vi);
|
||||
|
||||
}
|
||||
|
||||
static stream_processor *find_stream_processor(stream_set *set, ogg_page *page)
|
||||
{
|
||||
ogg_uint32_t serial = ogg_page_serialno(page);
|
||||
int i, found = 0;
|
||||
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! */
|
||||
found = 1;
|
||||
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;
|
||||
|
||||
{
|
||||
int res;
|
||||
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) {
|
||||
DPRINTF(E_WARN, L_SCAN, "Invalid header page, no packet found\n");
|
||||
null_start(stream);
|
||||
}
|
||||
else if(packet.bytes >= 7 && memcmp(packet.packet, "\001vorbis", 7)==0)
|
||||
vorbis_start(stream);
|
||||
else if(packet.bytes >= 8 && memcmp(packet.packet, "OggMIDI\0", 8)==0)
|
||||
other_start(stream, "MIDI");
|
||||
else
|
||||
other_start(stream, NULL);
|
||||
|
||||
res = ogg_stream_packetout(&stream->os, &packet);
|
||||
if(res > 0) {
|
||||
DPRINTF(E_WARN, L_SCAN, "Invalid header page in stream %d, "
|
||||
"contains multiple packets\n", stream->num);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
/* if(stream->serial == 0 || stream->serial == -1) {
|
||||
info(_("Note: Stream %d has serial number %d, which is legal but may "
|
||||
"cause problems with some tools."), stream->num, stream->serial);
|
||||
}*/
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
static int get_next_page(FILE *f, ogg_sync_state *sync, ogg_page *page,
|
||||
ogg_int64_t *written)
|
||||
{
|
||||
int ret;
|
||||
char *buffer;
|
||||
int bytes;
|
||||
|
||||
while((ret = ogg_sync_pageout(sync, page)) <= 0) {
|
||||
if(ret < 0)
|
||||
#ifdef _WIN32
|
||||
DPRINTF(E_WARN, L_SCAN, "Hole in data found at approximate offset %I64d bytes. Corrupted ogg.\n", *written);
|
||||
#else
|
||||
DPRINTF(E_WARN, L_SCAN, "Hole in data found at approximate offset %lld bytes. Corrupted ogg.\n", *written);
|
||||
#endif
|
||||
|
||||
buffer = ogg_sync_buffer(sync, CHUNK);
|
||||
bytes = fread(buffer, 1, CHUNK, f);
|
||||
if(bytes <= 0) {
|
||||
ogg_sync_wrote(sync, 0);
|
||||
return 0;
|
||||
}
|
||||
ogg_sync_wrote(sync, bytes);
|
||||
*written += bytes;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int scan_get_oggfileinfo(char *filename, MP3FILE *pmp3) {
|
||||
FILE *file = fopen(filename, "rb");
|
||||
ogg_sync_state sync;
|
||||
ogg_page page;
|
||||
stream_set *processors = create_stream_set();
|
||||
int gotpage = 0;
|
||||
ogg_int64_t written = 0;
|
||||
struct stat psb;
|
||||
FILE *f;
|
||||
OggVorbis_File vf;
|
||||
vorbis_comment *comment = NULL;
|
||||
vorbis_info *vi = NULL;
|
||||
char *val;
|
||||
|
||||
if(!file) {
|
||||
f = fopen(filename, "rb");
|
||||
if (f == NULL) {
|
||||
DPRINTF(E_FATAL, L_SCAN,
|
||||
"Error opening input file \"%s\": %s\n", filename,
|
||||
strerror(errno));
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
DPRINTF(E_INF, L_SCAN, "Processing file \"%s\"...\n\n", filename);
|
||||
|
||||
if (!stat(filename, &psb)) {
|
||||
pmp3->time_added = psb.st_mtime;
|
||||
if (psb.st_ctime < pmp3->time_added) {
|
||||
pmp3->time_added = psb.st_ctime;
|
||||
}
|
||||
pmp3->time_modified = psb.st_mtime;
|
||||
} else {
|
||||
DPRINTF(E_WARN, L_SCAN, "Error statting: %s\n", strerror(errno));
|
||||
if(!fseek(f,0,SEEK_END)) {
|
||||
pmp3->file_size=ftell(f);
|
||||
fseek(f,0,SEEK_SET);
|
||||
}
|
||||
|
||||
ogg_sync_init(&sync);
|
||||
|
||||
while(get_next_page(file, &sync, &page, &written)) {
|
||||
stream_processor *p = find_stream_processor(processors, &page);
|
||||
gotpage = 1;
|
||||
|
||||
if(!p) {
|
||||
DPRINTF(E_FATAL, L_SCAN, "Could not find a processor for stream, bailing\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
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.";
|
||||
}
|
||||
|
||||
DPRINTF(E_WARN, L_SCAN,
|
||||
"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) {
|
||||
DPRINTF(E_DBG, L_SCAN, "New logical stream (#%d, serial: %08x): type %s\n",
|
||||
p->num, p->serial, p->type);
|
||||
if(!p->start)
|
||||
DPRINTF(E_WARN, L_SCAN,
|
||||
"stream start flag not set on stream %d\n",
|
||||
p->num);
|
||||
}
|
||||
else if(p->start)
|
||||
DPRINTF(E_WARN, L_SCAN, "stream start flag found in mid-stream "
|
||||
"on stream %d\n", p->num);
|
||||
|
||||
if(p->seqno++ != ogg_page_pageno(&page)) {
|
||||
if(!p->lostseq)
|
||||
DPRINTF(E_WARN, L_SCAN,
|
||||
"sequence number gap in stream %d. Got page %ld "
|
||||
"when expecting page %ld. Indicates missing data.\n",
|
||||
p->num, ogg_page_pageno(&page), p->seqno - 1);
|
||||
p->seqno = ogg_page_pageno(&page);
|
||||
p->lostseq = 1;
|
||||
}
|
||||
else
|
||||
p->lostseq = 0;
|
||||
|
||||
if(!p->isillegal) {
|
||||
p->process_page(p, &page, pmp3);
|
||||
|
||||
if(p->end) {
|
||||
if(p->process_end)
|
||||
p->process_end(p, pmp3);
|
||||
DPRINTF(E_DBG, L_SCAN, "Logical stream %d ended\n", p->num);
|
||||
p->isillegal = 1;
|
||||
p->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS;
|
||||
}
|
||||
}
|
||||
if (ov_open(f, &vf, NULL, 0) != 0) {
|
||||
fclose(f);
|
||||
DPRINTF(E_FATAL, L_SCAN,
|
||||
"Error opening Vorbis stream in \"%s\"\n", filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
free_stream_set(processors);
|
||||
vi=ov_info(&vf,-1);
|
||||
if(vi) {
|
||||
DPRINTF(E_DBG,L_SCAN," Bitrates: %d %d %d\n",vi->bitrate_upper,
|
||||
vi->bitrate_nominal,vi->bitrate_lower);
|
||||
if(vi->bitrate_nominal) {
|
||||
pmp3->bitrate=vi->bitrate_nominal / 1000;
|
||||
} else if(vi->bitrate_upper) {
|
||||
pmp3->bitrate=vi->bitrate_upper / 1000;
|
||||
} else if(vi->bitrate_lower) {
|
||||
pmp3->bitrate=vi->bitrate_lower / 1000;
|
||||
}
|
||||
|
||||
ogg_sync_clear(&sync);
|
||||
|
||||
fclose(file);
|
||||
|
||||
if(!gotpage) {
|
||||
DPRINTF(E_FATAL, L_SCAN, "No ogg data found in file \"%s\".\n"
|
||||
"Input probably not ogg.\n", filename);
|
||||
return -1;
|
||||
DPRINTF(E_DBG,L_SCAN," Bitrates: %d",pmp3->bitrate);
|
||||
pmp3->samplerate=vi->rate;
|
||||
}
|
||||
|
||||
pmp3->song_length=ov_time_total(&vf,-1) * 1000;
|
||||
|
||||
comment = ov_comment(&vf, -1);
|
||||
if (comment != NULL) {
|
||||
if ((val = vorbis_comment_query(comment, "artist", 0)) != NULL)
|
||||
pmp3->artist = strdup(val);
|
||||
if ((val = vorbis_comment_query(comment, "title", 0)) != NULL)
|
||||
pmp3->title = strdup(val);
|
||||
if ((val = vorbis_comment_query(comment, "album", 0)) != NULL)
|
||||
pmp3->album = strdup(val);
|
||||
if ((val = vorbis_comment_query(comment, "genre", 0)) != NULL)
|
||||
pmp3->genre = strdup(val);
|
||||
if ((val = vorbis_comment_query(comment, "composer", 0)) != NULL)
|
||||
pmp3->composer = strdup(val);
|
||||
if ((val = vorbis_comment_query(comment, "comment", 0)) != NULL)
|
||||
pmp3->comment = strdup(val);
|
||||
if ((val = vorbis_comment_query(comment, "tracknumber", 0)) != NULL)
|
||||
pmp3->track = atoi(val);
|
||||
if ((val = vorbis_comment_query(comment, "discnumber", 0)) != NULL)
|
||||
pmp3->disc = atoi(val);
|
||||
if ((val = vorbis_comment_query(comment, "year", 0)) != NULL)
|
||||
pmp3->year = atoi(val);
|
||||
}
|
||||
ov_clear(&vf);
|
||||
/*fclose(f);*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*int main(int argc, char **argv) {
|
||||
int f, ret;
|
||||
|
||||
setlocale(LC_ALL, "");
|
||||
bindtextdomain(PACKAGE, LOCALEDIR);
|
||||
textdomain(PACKAGE);
|
||||
|
||||
process_file(argv[f]);
|
||||
}*/
|
||||
|
||||
|
@ -134,7 +134,7 @@ int server_side_convert_set(MP3FILE *pmp3)
|
||||
* @param path char * to the real filename.
|
||||
* @param offset off_t to the point in file where the streaming starts.
|
||||
*/
|
||||
FILE *server_side_convert_open(char *path, off_t offset)
|
||||
FILE *server_side_convert_open(char *path, off_t offset, unsigned long len_ms)
|
||||
{
|
||||
char *cmd;
|
||||
FILE *f;
|
||||
@ -142,8 +142,8 @@ FILE *server_side_convert_open(char *path, off_t offset)
|
||||
cmd=(char *)malloc(strlen(config.ssc_prog) +
|
||||
strlen(path) +
|
||||
64);
|
||||
sprintf(cmd, "%s \"%s\" %ld",
|
||||
config.ssc_prog, path, (long)offset);
|
||||
sprintf(cmd, "%s \"%s\" %ld %lu.%03lu",
|
||||
config.ssc_prog, path, (long)offset, len_ms / 1000, len_ms % 1000);
|
||||
f = popen(cmd, "r");
|
||||
return f;
|
||||
}
|
||||
|
@ -27,7 +27,9 @@
|
||||
|
||||
extern int server_side_convert_set(MP3FILE *pmp3);
|
||||
extern char *server_side_convert_path(char *path);
|
||||
extern FILE *server_side_convert_open(char *path, off_t offset);
|
||||
extern FILE *server_side_convert_open(char *path,
|
||||
off_t offset,
|
||||
unsigned long len_ms);
|
||||
extern void server_side_convert_close(FILE *f);
|
||||
|
||||
#endif /* _SCC_H_ */
|
||||
|
Loading…
Reference in New Issue
Block a user