Skip to content

Instantly share code, notes, and snippets.

@takeshich takeshich/metadata.c
Last active Aug 29, 2015

Embed
What would you like to do?
IODataのminidlnaのバグとか
/* MiniDLNA media server
* Copyright (C) 2008-2009 Justin Maggard
*
* This file is part of MiniDLNA.
*
* MiniDLNA is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* MiniDLNA 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 MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <libgen.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/param.h>
#include <fcntl.h>
#include <libexif/exif-loader.h>
#include <jpeglib.h>
#include <setjmp.h>
#include "libav.h"
#include "upnpglobalvars.h"
#include "tagutils/tagutils.h"
#include "image_utils.h"
#include "upnpreplyparse.h"
#include "tivo_utils.h"
#include "metadata.h"
#include "albumart.h"
#include "utils.h"
#include "sql.h"
#include "log.h"
#ifndef FF_PROFILE_H264_BASELINE
#define FF_PROFILE_H264_BASELINE 66
#endif
#ifndef FF_PROFILE_H264_MAIN
#define FF_PROFILE_H264_MAIN 77
#endif
#ifndef FF_PROFILE_H264_HIGH
#define FF_PROFILE_H264_HIGH 100
#endif
#define FF_PROFILE_SKIP -100
#if LIBAVCODEC_VERSION_MAJOR < 53
#define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO
#define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
#endif
#if LIBAVUTIL_VERSION_INT < ((50<<16)+(13<<8)+0)
#define av_strerror(x, y, z) snprintf(y, z, "%d", x)
#endif
#define FLAG_TITLE 0x00000001
#define FLAG_ARTIST 0x00000002
#define FLAG_ALBUM 0x00000004
#define FLAG_GENRE 0x00000008
#define FLAG_COMMENT 0x00000010
#define FLAG_CREATOR 0x00000020
#define FLAG_DATE 0x00000040
#define FLAG_DLNA_PN 0x00000080
#define FLAG_MIME 0x00000100
#define FLAG_DURATION 0x00000200
#define FLAG_RESOLUTION 0x00000400
#define FLAG_BITRATE 0x00000800
#define FLAG_FREQUENCY 0x00001000
#define FLAG_BPS 0x00002000
#define FLAG_CHANNELS 0x00004000
#define FLAG_ROTATION 0x00008000
/* Audio profile flags */
enum audio_profiles {
PROFILE_AUDIO_UNKNOWN,
PROFILE_AUDIO_MP3,
PROFILE_AUDIO_AC3,
PROFILE_AUDIO_WMA_BASE,
PROFILE_AUDIO_WMA_FULL,
PROFILE_AUDIO_WMA_PRO,
PROFILE_AUDIO_MP2,
PROFILE_AUDIO_PCM,
PROFILE_AUDIO_AAC,
PROFILE_AUDIO_AAC_MULT5,
PROFILE_AUDIO_AMR
};
static inline int
lav_open(AVFormatContext **ctx, const char *filename)
{
int ret;
#if LIBAVFORMAT_VERSION_INT >= ((53<<16)+(17<<8)+0)
ret = avformat_open_input(ctx, filename, NULL, NULL);
if (ret == 0)
avformat_find_stream_info(*ctx, NULL);
#else
ret = av_open_input_file(ctx, filename, NULL, 0, NULL);
if (ret == 0)
av_find_stream_info(*ctx);
#endif
return ret;
}
static inline void
lav_close(AVFormatContext *ctx)
{
#if LIBAVFORMAT_VERSION_INT >= ((53<<16)+(17<<8)+0)
avformat_close_input(&ctx);
#else
av_close_input_file(ctx);
#endif
}
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
# if LIBAVUTIL_VERSION_INT < ((51<<16)+(5<<8)+0) && !defined(FF_API_OLD_METADATA2)
#define AV_DICT_IGNORE_SUFFIX AV_METADATA_IGNORE_SUFFIX
#define av_dict_get av_metadata_get
typedef AVMetadataTag AVDictionaryEntry;
# endif
#endif
/* This function shamelessly copied from libdlna */
#define MPEG_TS_SYNC_CODE 0x47
#define MPEG_TS_PACKET_LENGTH 188
#define MPEG_TS_PACKET_LENGTH_DLNA 192 /* prepends 4 bytes to TS packet */
int
dlna_timestamp_is_present(const char *filename, int *raw_packet_size)
{
unsigned char buffer[3*MPEG_TS_PACKET_LENGTH_DLNA];
int fd, i;
/* read file header */
fd = open(filename, O_RDONLY);
if( fd < 0 )
return 0;
i = read(fd, buffer, MPEG_TS_PACKET_LENGTH_DLNA*3);
close(fd);
if( i < 0 )
return 0;
for( i = 0; i < MPEG_TS_PACKET_LENGTH_DLNA; i++ )
{
if( buffer[i] == MPEG_TS_SYNC_CODE )
{
if (buffer[i + MPEG_TS_PACKET_LENGTH_DLNA] == MPEG_TS_SYNC_CODE &&
buffer[i + MPEG_TS_PACKET_LENGTH_DLNA*2] == MPEG_TS_SYNC_CODE)
{
*raw_packet_size = MPEG_TS_PACKET_LENGTH_DLNA;
if (buffer[i+MPEG_TS_PACKET_LENGTH] == 0x00 &&
buffer[i+MPEG_TS_PACKET_LENGTH+1] == 0x00 &&
buffer[i+MPEG_TS_PACKET_LENGTH+2] == 0x00 &&
buffer[i+MPEG_TS_PACKET_LENGTH+3] == 0x00)
return 0;
else
return 1;
} else if (buffer[i + MPEG_TS_PACKET_LENGTH] == MPEG_TS_SYNC_CODE &&
buffer[i + MPEG_TS_PACKET_LENGTH*2] == MPEG_TS_SYNC_CODE) {
*raw_packet_size = MPEG_TS_PACKET_LENGTH;
return 0;
}
}
}
*raw_packet_size = 0;
return 0;
}
void
check_for_captions(const char *path, int64_t detailID)
{
char *file = malloc(MAXPATHLEN);
char *id = NULL;
sprintf(file, "%s", path);
strip_ext(file);
/* If we weren't given a detail ID, look for one. */
if( !detailID )
{
id = sql_get_text_field(db, "SELECT ID from DETAILS where (PATH > '%q.' and PATH <= '%q.z')"
" and MIME glob 'video/*' limit 1", file, file);
if( id )
{
DPRINTF(E_DEBUG, L_METADATA, "New file %s looks like a caption file.\n", path);
detailID = strtoll(id, NULL, 10);
}
else
{
DPRINTF(E_DEBUG, L_METADATA, "No file found for caption %s.\n", path);
goto no_source_video;
}
}
strcat(file, ".srt");
DPRINTF(E_DEBUG, L_METADATA, "search %s.\n", file);
if( access(file, R_OK) == 0 )
{
sql_exec(db, "INSERT into CAPTIONS"
" (ID, PATH) "
"VALUES"
" (%lld, %Q)", detailID, file);
DPRINTF(E_DEBUG, L_INOTIFY, "INSERT into CAPTIONS (%lld, %s).\n", detailID, file);
}
no_source_video:
if( id )
sqlite3_free(id);
free(file);
}
void
parse_nfo(const char *path, metadata_t *m)
{
FILE *nfo;
char buf[65536];
struct NameValueParserData xml;
struct stat file;
size_t nread;
char *val, *val2;
if( stat(path, &file) != 0 ||
file.st_size > 65536 )
{
DPRINTF(E_INFO, L_METADATA, "Not parsing very large .nfo file %s\n", path);
return;
}
DPRINTF(E_DEBUG, L_METADATA, "Parsing .nfo file: %s\n", path);
nfo = fopen(path, "r");
if( !nfo )
return;
nread = fread(&buf, 1, sizeof(buf), nfo);
ParseNameValue(buf, nread, &xml, 0);
//printf("\ttype: %s\n", GetValueFromNameValueList(&xml, "rootElement"));
val = GetValueFromNameValueList(&xml, "title");
if( val )
{
val2 = GetValueFromNameValueList(&xml, "episodetitle");
if( val2 )
xasprintf(&m->title, "%s - %s", val, val2);
else
m->title = strdup(val);
}
val = GetValueFromNameValueList(&xml, "plot");
if( val )
m->comment = strdup(val);
val = GetValueFromNameValueList(&xml, "capturedate");
if( val )
m->date = strdup(val);
val = GetValueFromNameValueList(&xml, "genre");
if( val )
{
free(m->genre);
m->genre = strdup(val);
}
val = GetValueFromNameValueList(&xml, "mime");
if( val )
{
free(m->mime);
m->mime = strdup(val);
}
ClearNameValueList(&xml);
fclose(nfo);
}
void
free_metadata(metadata_t *m, uint32_t flags)
{
if( flags & FLAG_TITLE )
free(m->title);
if( flags & FLAG_ARTIST )
free(m->artist);
if( flags & FLAG_ALBUM )
free(m->album);
if( flags & FLAG_GENRE )
free(m->genre);
if( flags & FLAG_CREATOR )
free(m->creator);
if( flags & FLAG_DATE )
free(m->date);
if( flags & FLAG_COMMENT )
free(m->comment);
if( flags & FLAG_DLNA_PN )
free(m->dlna_pn);
if( flags & FLAG_MIME )
free(m->mime);
if( flags & FLAG_DURATION )
free(m->duration);
if( flags & FLAG_RESOLUTION )
free(m->resolution);
if( flags & FLAG_BITRATE )
free(m->bitrate);
if( flags & FLAG_FREQUENCY )
free(m->frequency);
if( flags & FLAG_BPS )
free(m->bps);
if( flags & FLAG_CHANNELS )
free(m->channels);
if( flags & FLAG_ROTATION )
free(m->rotation);
}
int64_t
GetFolderMetadata(const char *name, const char *path, const char *artist, const char *genre, int64_t album_art)
{
int ret;
ret = sql_exec(db, "INSERT into DETAILS"
" (TITLE, PATH, CREATOR, ARTIST, GENRE, ALBUM_ART) "
"VALUES"
" ('%q', %Q, %Q, %Q, %Q, %lld);",
name, path, artist, artist, genre, album_art);
if( ret != SQLITE_OK )
ret = 0;
else
ret = sqlite3_last_insert_rowid(db);
return ret;
}
int64_t
GetAudioMetadata(const char *path, char *name)
{
char type[4];
static char lang[6] = { '\0' };
struct stat file;
int64_t ret;
char *esc_tag;
int i;
int64_t album_art = 0;
struct song_metadata song;
metadata_t m;
uint32_t free_flags = FLAG_MIME|FLAG_DURATION|FLAG_DLNA_PN|FLAG_DATE;
memset(&m, '\0', sizeof(metadata_t));
if ( stat(path, &file) != 0 )
return 0;
strip_ext(name);
if( ends_with(path, ".mp3") )
{
strcpy(type, "mp3");
m.mime = strdup("audio/mpeg");
}
else if( ends_with(path, ".m4a") || ends_with(path, ".mp4") ||
ends_with(path, ".aac") || ends_with(path, ".m4p") )
{
strcpy(type, "aac");
m.mime = strdup("audio/mp4");
}
else if( ends_with(path, ".3gp") )
{
strcpy(type, "aac");
m.mime = strdup("audio/3gpp");
}
else if( ends_with(path, ".wma") || ends_with(path, ".asf") )
{
strcpy(type, "asf");
m.mime = strdup("audio/x-ms-wma");
}
else if( ends_with(path, ".flac") || ends_with(path, ".fla") || ends_with(path, ".flc") )
{
strcpy(type, "flc");
m.mime = strdup("audio/x-flac");
}
else if( ends_with(path, ".wav") )
{
strcpy(type, "wav");
m.mime = strdup("audio/x-wav");
}
else if( ends_with(path, ".ogg") || ends_with(path, ".oga") )
{
strcpy(type, "ogg");
m.mime = strdup("audio/ogg");
}
else if( ends_with(path, ".pcm") )
{
strcpy(type, "pcm");
m.mime = strdup("audio/L16");
}
#if 1 // DSD Support // Paul Liao inXtron
else if( ends_with(path, ".dff") )
{
strcpy(type, "dsd");
m.mime = strdup("audio/dsd");
}
else if( ends_with(path, ".dsf") )
{
strcpy(type, "dsf");
m.mime = strdup("audio/x-dsd");
}
#endif
else
{
DPRINTF(E_WARN, L_GENERAL, "Unhandled file extension on %s\n", path);
return 0;
}
if( !(*lang) )
{
if( !getenv("LANG") )
strcpy(lang, "en_US");
else
strncpyt(lang, getenv("LANG"), sizeof(lang));
}
if( readtags((char *)path, &song, &file, lang, type) != 0 )
{
DPRINTF(E_WARN, L_GENERAL, "Cannot extract tags from %s!\n", path);
freetags(&song);
free_metadata(&m, free_flags);
return 0;
}
if( song.dlna_pn )
m.dlna_pn = strdup(song.dlna_pn);
if( song.year )
#if 1 // DSD Support // Enable on Bata 4 // Paul Liao inXtron 20131108
{
if (song.date ==0 && song.time ==0)
xasprintf(&m.date, "%04d-01-01,00:00", song.year);
else
xasprintf(&m.date, "%04d-%02d-%02d,%02d:%02d", song.year, song.date%100, song.date/100, song.time%100, song.time/100);
}
#else // Old Version
xasprintf(&m.date, "%04d-01-01", song.year);
#endif
xasprintf(&m.duration, "%d:%02d:%02d.%03d",
(song.song_length/3600000),
(song.song_length/60000%60),
(song.song_length/1000%60),
(song.song_length%1000));
if( song.title && *song.title )
{
m.title = trim(song.title);
if( (esc_tag = escape_tag(m.title, 0)) )
{
free_flags |= FLAG_TITLE;
m.title = esc_tag;
}
}
else
{
m.title = name;
}
for( i=ROLE_START; i<N_ROLE; i++ )
{
if( song.contributor[i] && *song.contributor[i] )
{
#if 1 // DSD Support // Enable on Bata 7 // Paul Liao 20140305
#if 0 // DSD Support // Enable on Bata 8 // Paul Liao 20140410
if ( (i == ROLE_ARTIST) || (i == ROLE_TRACKARTIST) )
{
m.artist = trim(song.contributor[i]);
if ( (esc_tag = escape_tag(m.artist, 0)) )
{
m.artist = esc_tag;
free_flags |= FLAG_ARTIST;
}
}
#else // DSD Support // Enable on Bata 9 // Paul Liao 20140415
m.artist = trim(song.contributor[i]);
if ( (esc_tag = escape_tag(m.artist, 0)) )
{
m.artist = esc_tag;
free_flags |= FLAG_CREATOR;
}
#endif
//DEBUG DPRINTF(E_DEBUG, L_METADATA, "m.artist = [%s], m.creator = [%s]\n", m.artist, m.creator);
#else // Old Version
m.creator = trim(song.contributor[i]);
if( strlen(m.creator) > 48 )
{
m.creator = strdup("Various Artists");
free_flags |= FLAG_CREATOR;
}
else if( (esc_tag = escape_tag(m.creator, 0)) )
{
m.creator = esc_tag;
free_flags |= FLAG_CREATOR;
}
#endif
break;
}
}
/* If there is a band associated with the album, use it for virtual containers. */
if( (i != ROLE_BAND) && (i != ROLE_ALBUMARTIST) )
{
if( song.contributor[ROLE_BAND] && *song.contributor[ROLE_BAND] )
{
i = ROLE_BAND;
m.artist = trim(song.contributor[i]);
#if 1 // DSD Support // Enable on Bata 7 // Paul Liao 20140226
if( (esc_tag = escape_tag(m.artist, 0)) )
{
m.artist = esc_tag;
free_flags |= FLAG_ARTIST;
}
//DEBUG DPRINTF(E_DEBUG, L_METADATA, "m.artist = [%s]\n", m.artist);
#else // Old Version
if( strlen(m.artist) > 48 )
{
m.artist = strdup("Various=Artists");
free_flags |= FLAG_ARTIST;
}
else if( (esc_tag = escape_tag(m.artist, 0)) )
{
m.artist = esc_tag;
free_flags |= FLAG_ARTIST;
}
#endif
}
}
if( song.album && *song.album )
{
m.album = trim(song.album);
if( (esc_tag = escape_tag(m.album, 0)) )
{
free_flags |= FLAG_ALBUM;
m.album = esc_tag;
}
}
if( song.genre && *song.genre )
{
m.genre = trim(song.genre);
if( (esc_tag = escape_tag(m.genre, 0)) )
{
free_flags |= FLAG_GENRE;
m.genre = esc_tag;
}
}
if( song.comment && *song.comment )
{
m.comment = trim(song.comment);
if( (esc_tag = escape_tag(m.comment, 0)) )
{
free_flags |= FLAG_COMMENT;
m.comment = esc_tag;
}
}
album_art = find_album_art(path, song.image, song.image_size);
ret = sql_exec(db, "INSERT into DETAILS"
" (PATH, SIZE, TIMESTAMP, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE,"
" TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, DISC, TRACK, DLNA_PN, MIME, ALBUM_ART) "
"VALUES"
" (%Q, %lld, %ld, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, %d, %Q, '%s', %lld);",
path, (long long)file.st_size, file.st_mtime, m.duration, song.channels, song.bitrate, song.samplerate, m.date,
m.title, m.creator, m.artist, m.album, m.genre, m.comment, song.disc, song.track,
m.dlna_pn, song.mime?song.mime:m.mime, album_art);
if( ret != SQLITE_OK )
{
fprintf(stderr, "Error inserting details for '%s'!\n", path);
ret = 0;
}
else
{
ret = sqlite3_last_insert_rowid(db);
}
freetags(&song);
free_metadata(&m, free_flags);
return ret;
}
/* For libjpeg error handling */
jmp_buf setjmp_buffer;
static void
libjpeg_error_handler(j_common_ptr cinfo)
{
cinfo->err->output_message (cinfo);
longjmp(setjmp_buffer, 1);
return;
}
int64_t
GetImageMetadata(const char *path, char *name)
{
ExifData *ed;
ExifEntry *e = NULL;
ExifLoader *l;
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
FILE *infile;
int width=0, height=0, thumb=0;
char make[32], model[64] = {'\0'};
char b[1024];
struct stat file;
int64_t ret;
image_s *imsrc;
metadata_t m;
uint32_t free_flags = 0xFFFFFFFF;
memset(&m, '\0', sizeof(metadata_t));
//DEBUG DPRINTF(E_DEBUG, L_METADATA, "Parsing %s...\n", path);
if ( stat(path, &file) != 0 )
return 0;
strip_ext(name);
//DEBUG DPRINTF(E_DEBUG, L_METADATA, " * size: %jd\n", file.st_size);
/* MIME hard-coded to JPEG for now, until we add PNG support */
m.mime = strdup("image/jpeg");
l = exif_loader_new();
exif_loader_write_file(l, path);
ed = exif_loader_get_data(l);
exif_loader_unref(l);
if( !ed )
goto no_exifdata;
e = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL);
if( e || (e = exif_content_get_entry(ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_DIGITIZED)) )
{
m.date = strdup(exif_entry_get_value(e, b, sizeof(b)));
if( strlen(m.date) > 10 )
{
m.date[4] = '-';
m.date[7] = '-';
m.date[10] = 'T';
}
else {
free(m.date);
m.date = NULL;
}
}
else {
/* One last effort to get the date from XMP */
image_get_jpeg_date_xmp(path, &m.date);
}
//DEBUG DPRINTF(E_DEBUG, L_METADATA, " * date: %s\n", m.date);
e = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_MAKE);
if( e )
{
strncpyt(make, exif_entry_get_value(e, b, sizeof(b)), sizeof(make));
e = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_MODEL);
if( e )
{
strncpyt(model, exif_entry_get_value(e, b, sizeof(b)), sizeof(model));
if( !strcasestr(model, make) )
snprintf(model, sizeof(model), "%s %s", make, exif_entry_get_value(e, b, sizeof(b)));
m.creator = escape_tag(trim(model), 1);
}
}
//DEBUG DPRINTF(E_DEBUG, L_METADATA, " * model: %s\n", model);
e = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION);
if( e )
{
int rotate;
switch( exif_get_short(e->data, exif_data_get_byte_order(ed)) )
{
case 3:
rotate = 180;
break;
case 6:
rotate = 90;
break;
case 8:
rotate = 270;
break;
default:
rotate = 0;
break;
}
if( rotate )
xasprintf(&m.rotation, "%d", rotate);
}
if( ed->size )
{
/* We might need to verify that the thumbnail is 160x160 or smaller */
if( ed->size > 12000 )
{
imsrc = image_new_from_jpeg(NULL, 0, (char *)ed->data, ed->size, 1, ROTATE_NONE);
if( imsrc )
{
if( (imsrc->width <= 160) && (imsrc->height <= 160) )
thumb = 1;
image_free(imsrc);
}
}
else
thumb = 1;
}
//DEBUG DPRINTF(E_DEBUG, L_METADATA, " * thumbnail: %d\n", thumb);
exif_data_unref(ed);
no_exifdata:
/* If SOF parsing fails, then fall through to reading the JPEG data with libjpeg to get the resolution */
if( image_get_jpeg_resolution(path, &width, &height) != 0 || !width || !height )
{
infile = fopen(path, "r");
if( infile )
{
cinfo.err = jpeg_std_error(&jerr);
jerr.error_exit = libjpeg_error_handler;
jpeg_create_decompress(&cinfo);
if( setjmp(setjmp_buffer) )
goto error;
jpeg_stdio_src(&cinfo, infile);
jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo);
width = cinfo.output_width;
height = cinfo.output_height;
error:
jpeg_destroy_decompress(&cinfo);
fclose(infile);
}
}
//DEBUG DPRINTF(E_DEBUG, L_METADATA, " * resolution: %dx%d\n", width, height);
if( !width || !height )
{
free_metadata(&m, free_flags);
return 0;
}
if( width <= 640 && height <= 480 )
m.dlna_pn = strdup("JPEG_SM");
else if( width <= 1024 && height <= 768 )
m.dlna_pn = strdup("JPEG_MED");
else if( (width <= 4096 && height <= 4096) || !GETFLAG(DLNA_STRICT_MASK) )
m.dlna_pn = strdup("JPEG_LRG");
xasprintf(&m.resolution, "%dx%d", width, height);
ret = sql_exec(db, "INSERT into DETAILS"
" (PATH, TITLE, SIZE, TIMESTAMP, DATE, RESOLUTION,"
" ROTATION, THUMBNAIL, CREATOR, DLNA_PN, MIME) "
"VALUES"
" (%Q, '%q', %lld, %ld, %Q, %Q, %Q, %d, %Q, %Q, %Q);",
path, name, (long long)file.st_size, file.st_mtime, m.date, m.resolution,
m.rotation, thumb, m.creator, m.dlna_pn, m.mime);
if( ret != SQLITE_OK )
{
fprintf(stderr, "Error inserting details for '%s'!\n", path);
ret = 0;
}
else
{
ret = sqlite3_last_insert_rowid(db);
}
free_metadata(&m, free_flags);
return ret;
}
int64_t
GetVideoMetadata(const char *path, char *name)
{
struct stat file;
int ret, i;
struct tm *modtime;
AVFormatContext *ctx = NULL;
AVCodecContext *ac = NULL, *vc = NULL;
int audio_stream = -1, video_stream = -1;
enum audio_profiles audio_profile = PROFILE_AUDIO_UNKNOWN;
char fourcc[4];
int64_t album_art = 0;
char nfo[MAXPATHLEN], *ext;
struct song_metadata video;
metadata_t m;
uint32_t free_flags = 0xFFFFFFFF;
char *path_cpy, *basepath;
memset(&m, '\0', sizeof(m));
memset(&video, '\0', sizeof(video));
//DEBUG DPRINTF(E_DEBUG, L_METADATA, "Parsing video %s...\n", name);
if ( stat(path, &file) != 0 )
return 0;
strip_ext(name);
//DEBUG DPRINTF(E_DEBUG, L_METADATA, " * size: %jd\n", file.st_size);
ret = lav_open(&ctx, path);
if( ret != 0 )
{
char err[128];
av_strerror(ret, err, sizeof(err));
DPRINTF(E_WARN, L_METADATA, "Opening %s failed! [%s]\n", path, err);
return 0;
}
//dump_format(ctx, 0, NULL, 0);
for( i=0; i<ctx->nb_streams; i++)
{
if( audio_stream == -1 &&
ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO )
{
audio_stream = i;
ac = ctx->streams[audio_stream]->codec;
continue;
}
else if( video_stream == -1 &&
ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO )
{
video_stream = i;
vc = ctx->streams[video_stream]->codec;
continue;
}
}
path_cpy = strdup(path);
basepath = basename(path_cpy);
if( !vc )
{
/* This must not be a video file. */
lav_close(ctx);
if( !is_audio(path) )
DPRINTF(E_DEBUG, L_METADATA, "File %s does not contain a video stream.\n", basepath);
free(path_cpy);
return 0;
}
if( ac )
{
aac_object_type_t aac_type = AAC_INVALID;
switch( ac->codec_id )
{
case CODEC_ID_MP3:
audio_profile = PROFILE_AUDIO_MP3;
break;
case CODEC_ID_AAC:
if( !ac->extradata_size ||
!ac->extradata )
{
DPRINTF(E_DEBUG, L_METADATA, "No AAC type\n");
}
else
{
uint8_t data;
memcpy(&data, ac->extradata, 1);
aac_type = data >> 3;
}
switch( aac_type )
{
/* AAC Low Complexity variants */
case AAC_LC:
case AAC_LC_ER:
if( ac->sample_rate < 8000 ||
ac->sample_rate > 48000 )
{
DPRINTF(E_DEBUG, L_METADATA, "Unsupported AAC: sample rate is not 8000 < %d < 48000\n",
ac->sample_rate);
break;
}
/* AAC @ Level 1/2 */
if( ac->channels <= 2 &&
ac->bit_rate <= 576000 )
audio_profile = PROFILE_AUDIO_AAC;
else if( ac->channels <= 6 &&
ac->bit_rate <= 1440000 )
audio_profile = PROFILE_AUDIO_AAC_MULT5;
else
DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC: %d channels, %d bitrate\n",
ac->channels,
ac->bit_rate);
break;
default:
DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC type [%d]\n", aac_type);
break;
}
break;
case CODEC_ID_AC3:
case CODEC_ID_DTS:
audio_profile = PROFILE_AUDIO_AC3;
break;
case CODEC_ID_WMAV1:
case CODEC_ID_WMAV2:
/* WMA Baseline: stereo, up to 48 KHz, up to 192,999 bps */
if ( ac->bit_rate <= 193000 )
audio_profile = PROFILE_AUDIO_WMA_BASE;
/* WMA Full: stereo, up to 48 KHz, up to 385 Kbps */
else if ( ac->bit_rate <= 385000 )
audio_profile = PROFILE_AUDIO_WMA_FULL;
break;
#if LIBAVCODEC_VERSION_INT > ((51<<16)+(50<<8)+1)
case CODEC_ID_WMAPRO:
audio_profile = PROFILE_AUDIO_WMA_PRO;
break;
#endif
case CODEC_ID_MP2:
audio_profile = PROFILE_AUDIO_MP2;
break;
case CODEC_ID_AMR_NB:
audio_profile = PROFILE_AUDIO_AMR;
break;
default:
if( (ac->codec_id >= CODEC_ID_PCM_S16LE) &&
(ac->codec_id < CODEC_ID_ADPCM_IMA_QT) )
audio_profile = PROFILE_AUDIO_PCM;
else
DPRINTF(E_DEBUG, L_METADATA, "Unhandled audio codec [0x%X]\n", ac->codec_id);
break;
}
xasprintf(&m.frequency, "%u", ac->sample_rate);
#if LIBAVCODEC_VERSION_INT < (52<<16)
xasprintf(&m.bps, "%u", ac->bits_per_sample);
#else
xasprintf(&m.bps, "%u", ac->bits_per_coded_sample);
#endif
xasprintf(&m.channels, "%u", ac->channels);
}
if( vc )
{
int off;
int duration, hours, min, sec, ms;
ts_timestamp_t ts_timestamp = NONE;
DPRINTF(E_DEBUG, L_METADATA, "Container: '%s' [%s]\n", ctx->iformat->name, basepath);
xasprintf(&m.resolution, "%dx%d", vc->width, vc->height);
if( ctx->bit_rate > 8 )
xasprintf(&m.bitrate, "%u", ctx->bit_rate / 8);
if( ctx->duration > 0 ) {
duration = (int)(ctx->duration / AV_TIME_BASE);
hours = (int)(duration / 3600);
min = (int)(duration / 60 % 60);
sec = (int)(duration % 60);
ms = (int)(ctx->duration / (AV_TIME_BASE/1000) % 1000);
xasprintf(&m.duration, "%d:%02d:%02d.%03d", hours, min, sec, ms);
}
/* NOTE: The DLNA spec only provides for ASF (WMV), TS, PS, and MP4 containers.
* Skip DLNA parsing for everything else. */
if( strcmp(ctx->iformat->name, "avi") == 0 )
{
xasprintf(&m.mime, "video/x-msvideo");
if( vc->codec_id == CODEC_ID_MPEG4 )
{
fourcc[0] = vc->codec_tag & 0xff;
fourcc[1] = vc->codec_tag>>8 & 0xff;
fourcc[2] = vc->codec_tag>>16 & 0xff;
fourcc[3] = vc->codec_tag>>24 & 0xff;
if( memcmp(fourcc, "XVID", 4) == 0 ||
memcmp(fourcc, "DX50", 4) == 0 ||
memcmp(fourcc, "DIVX", 4) == 0 )
xasprintf(&m.creator, "DiVX");
}
}
else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 &&
ends_with(path, ".mov") )
xasprintf(&m.mime, "video/quicktime");
else if( strncmp(ctx->iformat->name, "matroska", 8) == 0 )
xasprintf(&m.mime, "video/x-matroska");
else if( strcmp(ctx->iformat->name, "flv") == 0 )
xasprintf(&m.mime, "video/x-flv");
if( m.mime )
goto video_no_dlna;
switch( vc->codec_id )
{
case CODEC_ID_MPEG1VIDEO:
if( strcmp(ctx->iformat->name, "mpeg") == 0 )
{
if( (vc->width == 352) &&
(vc->height <= 288) )
{
m.dlna_pn = strdup("MPEG1");
}
xasprintf(&m.mime, "video/mpeg");
}
break;
case CODEC_ID_MPEG2VIDEO:
m.dlna_pn = malloc(64);
off = sprintf(m.dlna_pn, "MPEG_");
if( strcmp(ctx->iformat->name, "mpegts") == 0 )
{
int raw_packet_size;
int dlna_ts_present = dlna_timestamp_is_present(path, &raw_packet_size);
DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is %s MPEG2 TS packet size %d\n",
video_stream, basepath, m.resolution, raw_packet_size);
off += sprintf(m.dlna_pn+off, "TS_");
if( (vc->width >= 1280) &&
(vc->height >= 720) )
{
off += sprintf(m.dlna_pn+off, "HD_NA");
}
else
{
off += sprintf(m.dlna_pn+off, "SD_");
if( (vc->height == 576) ||
(vc->height == 288) )
off += sprintf(m.dlna_pn+off, "EU");
else
off += sprintf(m.dlna_pn+off, "NA");
}
if( raw_packet_size == MPEG_TS_PACKET_LENGTH_DLNA )
{
if (dlna_ts_present)
ts_timestamp = VALID;
else
ts_timestamp = EMPTY;
}
else if( raw_packet_size != MPEG_TS_PACKET_LENGTH )
{
DPRINTF(E_DEBUG, L_METADATA, "Unsupported DLNA TS packet size [%d] (%s)\n",
raw_packet_size, basepath);
free(m.dlna_pn);
m.dlna_pn = NULL;
}
switch( ts_timestamp )
{
case NONE:
xasprintf(&m.mime, "video/mpeg");
if( m.dlna_pn )
off += sprintf(m.dlna_pn+off, "_ISO");
break;
case VALID:
off += sprintf(m.dlna_pn+off, "_T");
case EMPTY:
xasprintf(&m.mime, "video/vnd.dlna.mpeg-tts");
default:
break;
}
}
else if( strcmp(ctx->iformat->name, "mpeg") == 0 )
{
DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is %s MPEG2 PS\n",
video_stream, basepath, m.resolution);
off += sprintf(m.dlna_pn+off, "PS_");
if( (vc->height == 576) ||
(vc->height == 288) )
off += sprintf(m.dlna_pn+off, "PAL");
else
off += sprintf(m.dlna_pn+off, "NTSC");
xasprintf(&m.mime, "video/mpeg");
}
else
{
DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s [%s] is %s non-DLNA MPEG2\n",
video_stream, basepath, ctx->iformat->name, m.resolution);
free(m.dlna_pn);
m.dlna_pn = NULL;
}
break;
case CODEC_ID_H264:
m.dlna_pn = malloc(128);
off = sprintf(m.dlna_pn, "AVC_");
if( strcmp(ctx->iformat->name, "mpegts") == 0 )
{
AVRational display_aspect_ratio;
int fps, interlaced;
int raw_packet_size;
int dlna_ts_present = dlna_timestamp_is_present(path, &raw_packet_size);
off += sprintf(m.dlna_pn+off, "TS_");
if (vc->sample_aspect_ratio.num) {
av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den,
vc->width * vc->sample_aspect_ratio.num,
vc->height * vc->sample_aspect_ratio.den,
1024*1024);
}
if (ctx->streams[video_stream]->r_frame_rate.den)
fps = ctx->streams[video_stream]->r_frame_rate.num / ctx->streams[video_stream]->r_frame_rate.den;
else
fps = 0;
interlaced = vc->time_base.den ? (ctx->streams[video_stream]->r_frame_rate.num / vc->time_base.den) : 0;
if( ((((vc->width == 1920 || vc->width == 1440) && vc->height == 1080) ||
(vc->width == 720 && vc->height == 480)) && fps == 59 && interlaced) ||
((vc->width == 1280 && vc->height == 720) && fps == 59 && !interlaced) )
{
if( (vc->profile == FF_PROFILE_H264_MAIN || vc->profile == FF_PROFILE_H264_HIGH) &&
audio_profile == PROFILE_AUDIO_AC3 )
{
off += sprintf(m.dlna_pn+off, "HD_60_");
vc->profile = FF_PROFILE_SKIP;
}
}
else if( ((vc->width == 1920 && vc->height == 1080) ||
(vc->width == 1440 && vc->height == 1080) ||
(vc->width == 1280 && vc->height == 720) ||
(vc->width == 720 && vc->height == 576)) &&
interlaced && fps == 50 )
{
if( (vc->profile == FF_PROFILE_H264_MAIN || vc->profile == FF_PROFILE_H264_HIGH) &&
audio_profile == PROFILE_AUDIO_AC3 )
{
off += sprintf(m.dlna_pn+off, "HD_50_");
vc->profile = FF_PROFILE_SKIP;
}
}
switch( vc->profile )
{
case FF_PROFILE_H264_BASELINE:
off += sprintf(m.dlna_pn+off, "BL_");
if( vc->width <= 352 &&
vc->height <= 288 &&
vc->bit_rate <= 384000 )
{
off += sprintf(m.dlna_pn+off, "CIF15_");
break;
}
else if( vc->width <= 352 &&
vc->height <= 288 &&
vc->bit_rate <= 3000000 )
{
off += sprintf(m.dlna_pn+off, "CIF30_");
break;
}
/* Fall back to Main Profile if it doesn't match a Baseline DLNA profile. */
else
off -= 3;
default:
case FF_PROFILE_H264_MAIN:
off += sprintf(m.dlna_pn+off, "MP_");
if( vc->profile != FF_PROFILE_H264_BASELINE &&
vc->profile != FF_PROFILE_H264_MAIN )
{
DPRINTF(E_DEBUG, L_METADATA, "Unknown AVC profile %d; assuming MP. [%s]\n",
vc->profile, basepath);
}
if( vc->width <= 720 &&
vc->height <= 576 &&
vc->bit_rate <= 10000000 )
{
off += sprintf(m.dlna_pn+off, "SD_");
}
else if( vc->width <= 1920 &&
vc->height <= 1152 &&
vc->bit_rate <= 20000000 )
{
off += sprintf(m.dlna_pn+off, "HD_");
}
else
{
DPRINTF(E_DEBUG, L_METADATA, "Unsupported h.264 video profile! [%s, %dx%d, %dbps : %s]\n",
m.dlna_pn, vc->width, vc->height, vc->bit_rate, basepath);
free(m.dlna_pn);
m.dlna_pn = NULL;
}
break;
case FF_PROFILE_H264_HIGH:
off += sprintf(m.dlna_pn+off, "HP_");
if( vc->width <= 1920 &&
vc->height <= 1152 &&
vc->bit_rate <= 30000000 &&
audio_profile == PROFILE_AUDIO_AC3 )
{
off += sprintf(m.dlna_pn+off, "HD_");
}
else
{
DPRINTF(E_DEBUG, L_METADATA, "Unsupported h.264 HP video profile! [%dbps, %d audio : %s]\n",
vc->bit_rate, audio_profile, basepath);
free(m.dlna_pn);
m.dlna_pn = NULL;
}
break;
case FF_PROFILE_SKIP:
break;
}
if( !m.dlna_pn )
break;
switch( audio_profile )
{
case PROFILE_AUDIO_MP3:
off += sprintf(m.dlna_pn+off, "MPEG1_L3");
break;
case PROFILE_AUDIO_AC3:
off += sprintf(m.dlna_pn+off, "AC3");
break;
case PROFILE_AUDIO_AAC:
case PROFILE_AUDIO_AAC_MULT5:
off += sprintf(m.dlna_pn+off, "AAC_MULT5");
break;
default:
DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for %s file [%s]\n",
m.dlna_pn, basepath);
free(m.dlna_pn);
m.dlna_pn = NULL;
break;
}
if( !m.dlna_pn )
break;
if( raw_packet_size == MPEG_TS_PACKET_LENGTH_DLNA )
{
if( vc->profile == FF_PROFILE_H264_HIGH ||
dlna_ts_present )
ts_timestamp = VALID;
else
ts_timestamp = EMPTY;
}
else if( raw_packet_size != MPEG_TS_PACKET_LENGTH )
{
DPRINTF(E_DEBUG, L_METADATA, "Unsupported DLNA TS packet size [%d] (%s)\n",
raw_packet_size, basepath);
free(m.dlna_pn);
m.dlna_pn = NULL;
}
switch( ts_timestamp )
{
case NONE:
if( m.dlna_pn )
off += sprintf(m.dlna_pn+off, "_ISO");
break;
case VALID:
off += sprintf(m.dlna_pn+off, "_T");
case EMPTY:
xasprintf(&m.mime, "video/vnd.dlna.mpeg-tts");
default:
break;
}
}
else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 )
{
off += sprintf(m.dlna_pn+off, "MP4_");
switch( vc->profile ) {
case FF_PROFILE_H264_BASELINE:
if( vc->width <= 352 &&
vc->height <= 288 )
{
if( ctx->bit_rate < 600000 )
off += sprintf(m.dlna_pn+off, "BL_CIF15_");
else if( ctx->bit_rate < 5000000 )
off += sprintf(m.dlna_pn+off, "BL_CIF30_");
else
goto mp4_mp_fallback;
if( audio_profile == PROFILE_AUDIO_AMR )
{
off += sprintf(m.dlna_pn+off, "AMR");
}
else if( audio_profile == PROFILE_AUDIO_AAC )
{
off += sprintf(m.dlna_pn+off, "AAC_");
if( ctx->bit_rate < 520000 )
{
off += sprintf(m.dlna_pn+off, "520");
}
else if( ctx->bit_rate < 940000 )
{
off += sprintf(m.dlna_pn+off, "940");
}
else
{
off -= 13;
goto mp4_mp_fallback;
}
}
else
{
off -= 9;
goto mp4_mp_fallback;
}
}
else if( vc->width <= 720 &&
vc->height <= 576 )
{
if( vc->level == 30 &&
audio_profile == PROFILE_AUDIO_AAC &&
ctx->bit_rate <= 5000000 )
off += sprintf(m.dlna_pn+off, "BL_L3L_SD_AAC");
else if( vc->level <= 31 &&
audio_profile == PROFILE_AUDIO_AAC &&
ctx->bit_rate <= 15000000 )
off += sprintf(m.dlna_pn+off, "BL_L31_HD_AAC");
else
goto mp4_mp_fallback;
}
else if( vc->width <= 1280 &&
vc->height <= 720 )
{
if( vc->level <= 31 &&
audio_profile == PROFILE_AUDIO_AAC &&
ctx->bit_rate <= 15000000 )
off += sprintf(m.dlna_pn+off, "BL_L31_HD_AAC");
else if( vc->level <= 32 &&
audio_profile == PROFILE_AUDIO_AAC &&
ctx->bit_rate <= 21000000 )
off += sprintf(m.dlna_pn+off, "BL_L32_HD_AAC");
else
goto mp4_mp_fallback;
}
else
goto mp4_mp_fallback;
break;
case FF_PROFILE_H264_MAIN:
mp4_mp_fallback:
off += sprintf(m.dlna_pn+off, "MP_");
/* AVC MP4 SD profiles - 10 Mbps max */
if( vc->width <= 720 &&
vc->height <= 576 &&
vc->bit_rate <= 10000000 )
{
sprintf(m.dlna_pn+off, "SD_");
if( audio_profile == PROFILE_AUDIO_AC3 )
off += sprintf(m.dlna_pn+off, "AC3");
else if( audio_profile == PROFILE_AUDIO_AAC ||
audio_profile == PROFILE_AUDIO_AAC_MULT5 )
off += sprintf(m.dlna_pn+off, "AAC_MULT5");
else if( audio_profile == PROFILE_AUDIO_MP3 )
off += sprintf(m.dlna_pn+off, "MPEG1_L3");
else
m.dlna_pn[10] = '\0';
}
else if( vc->width <= 1280 &&
vc->height <= 720 &&
vc->bit_rate <= 15000000 &&
audio_profile == PROFILE_AUDIO_AAC )
{
off += sprintf(m.dlna_pn+off, "HD_720p_AAC");
}
else if( vc->width <= 1920 &&
vc->height <= 1080 &&
vc->bit_rate <= 21000000 &&
audio_profile == PROFILE_AUDIO_AAC )
{
off += sprintf(m.dlna_pn+off, "HD_1080i_AAC");
}
if( strlen(m.dlna_pn) <= 11 )
{
DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for %s file %s\n",
m.dlna_pn, basepath);
free(m.dlna_pn);
m.dlna_pn = NULL;
}
break;
case FF_PROFILE_H264_HIGH:
if( vc->width <= 1920 &&
vc->height <= 1080 &&
vc->bit_rate <= 25000000 &&
audio_profile == PROFILE_AUDIO_AAC )
{
off += sprintf(m.dlna_pn+off, "HP_HD_AAC");
}
break;
default:
DPRINTF(E_DEBUG, L_METADATA, "AVC profile [%d] not recognized for file %s\n",
vc->profile, basepath);
free(m.dlna_pn);
m.dlna_pn = NULL;
break;
}
}
else
{
free(m.dlna_pn);
m.dlna_pn = NULL;
}
DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is h.264\n", video_stream, basepath);
break;
case CODEC_ID_MPEG4:
fourcc[0] = vc->codec_tag & 0xff;
fourcc[1] = vc->codec_tag>>8 & 0xff;
fourcc[2] = vc->codec_tag>>16 & 0xff;
fourcc[3] = vc->codec_tag>>24 & 0xff;
DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is MPEG4 [%c%c%c%c/0x%X]\n",
video_stream, basepath,
isprint(fourcc[0]) ? fourcc[0] : '_',
isprint(fourcc[1]) ? fourcc[1] : '_',
isprint(fourcc[2]) ? fourcc[2] : '_',
isprint(fourcc[3]) ? fourcc[3] : '_',
vc->codec_tag);
if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 )
{
m.dlna_pn = malloc(128);
off = sprintf(m.dlna_pn, "MPEG4_P2_");
if( ends_with(path, ".3gp") )
{
xasprintf(&m.mime, "video/3gpp");
switch( audio_profile )
{
case PROFILE_AUDIO_AAC:
off += sprintf(m.dlna_pn+off, "3GPP_SP_L0B_AAC");
break;
case PROFILE_AUDIO_AMR:
off += sprintf(m.dlna_pn+off, "3GPP_SP_L0B_AMR");
break;
default:
DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for MPEG4-P2 3GP/0x%X file %s\n",
ac->codec_id, basepath);
free(m.dlna_pn);
m.dlna_pn = NULL;
break;
}
}
else
{
if( ctx->bit_rate <= 1000000 &&
audio_profile == PROFILE_AUDIO_AAC )
{
off += sprintf(m.dlna_pn+off, "MP4_ASP_AAC");
}
else if( ctx->bit_rate <= 4000000 &&
vc->width <= 640 &&
vc->height <= 480 &&
audio_profile == PROFILE_AUDIO_AAC )
{
off += sprintf(m.dlna_pn+off, "MP4_SP_VGA_AAC");
}
else
{
DPRINTF(E_DEBUG, L_METADATA, "Unsupported h.264 video profile! [%dx%d, %dbps]\n",
vc->width,
vc->height,
ctx->bit_rate);
free(m.dlna_pn);
m.dlna_pn = NULL;
}
}
}
break;
case CODEC_ID_WMV3:
/* I'm not 100% sure this is correct, but it works on everything I could get my hands on */
if( vc->extradata_size > 0 )
{
if( !((vc->extradata[0] >> 3) & 1) )
vc->level = 0;
if( !((vc->extradata[0] >> 6) & 1) )
vc->profile = 0;
}
case CODEC_ID_VC1:
if( strcmp(ctx->iformat->name, "asf") != 0 )
{
DPRINTF(E_DEBUG, L_METADATA, "Skipping DLNA parsing for non-ASF VC1 file %s\n", path);
break;
}
m.dlna_pn = malloc(64);
off = sprintf(m.dlna_pn, "WMV");
DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is VC1\n", video_stream, basepath);
xasprintf(&m.mime, "video/x-ms-wmv");
if( (vc->width <= 176) &&
(vc->height <= 144) &&
(vc->level == 0) )
{
off += sprintf(m.dlna_pn+off, "SPLL_");
switch( audio_profile )
{
case PROFILE_AUDIO_MP3:
off += sprintf(m.dlna_pn+off, "MP3");
break;
case PROFILE_AUDIO_WMA_BASE:
off += sprintf(m.dlna_pn+off, "BASE");
break;
default:
DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for WMVSPLL/0x%X file %s\n",
audio_profile, basepath);
free(m.dlna_pn);
m.dlna_pn = NULL;
break;
}
}
else if( (vc->width <= 352) &&
(vc->height <= 288) &&
(vc->profile == 0) &&
(ctx->bit_rate/8 <= 384000) )
{
off += sprintf(m.dlna_pn+off, "SPML_");
switch( audio_profile )
{
case PROFILE_AUDIO_MP3:
off += sprintf(m.dlna_pn+off, "MP3");
break;
case PROFILE_AUDIO_WMA_BASE:
off += sprintf(m.dlna_pn+off, "BASE");
break;
default:
DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for WMVSPML/0x%X file %s\n",
audio_profile, basepath);
free(m.dlna_pn);
m.dlna_pn = NULL;
break;
}
}
else if( (vc->width <= 720) &&
(vc->height <= 576) &&
(ctx->bit_rate/8 <= 10000000) )
{
off += sprintf(m.dlna_pn+off, "MED_");
switch( audio_profile )
{
case PROFILE_AUDIO_WMA_PRO:
off += sprintf(m.dlna_pn+off, "PRO");
break;
case PROFILE_AUDIO_WMA_FULL:
off += sprintf(m.dlna_pn+off, "FULL");
break;
case PROFILE_AUDIO_WMA_BASE:
off += sprintf(m.dlna_pn+off, "BASE");
break;
default:
DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for WMVMED/0x%X file %s\n",
audio_profile, basepath);
free(m.dlna_pn);
m.dlna_pn = NULL;
break;
}
}
else if( (vc->width <= 1920) &&
(vc->height <= 1080) &&
(ctx->bit_rate/8 <= 20000000) )
{
off += sprintf(m.dlna_pn+off, "HIGH_");
switch( audio_profile )
{
case PROFILE_AUDIO_WMA_PRO:
off += sprintf(m.dlna_pn+off, "PRO");
break;
case PROFILE_AUDIO_WMA_FULL:
off += sprintf(m.dlna_pn+off, "FULL");
break;
default:
DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for WMVHIGH/0x%X file %s\n",
audio_profile, basepath);
free(m.dlna_pn);
m.dlna_pn = NULL;
break;
}
}
break;
case CODEC_ID_MSMPEG4V3:
xasprintf(&m.mime, "video/x-msvideo");
default:
DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is %s [type %d]\n",
video_stream, basepath, m.resolution, vc->codec_id);
break;
}
}
if( strcmp(ctx->iformat->name, "asf") == 0 )
{
if( readtags((char *)path, &video, &file, "en_US", "asf") == 0 )
{
if( video.title && *video.title )
{
m.title = escape_tag(trim(video.title), 1);
}
if( video.genre && *video.genre )
{
m.genre = escape_tag(trim(video.genre), 1);
}
if( video.contributor[ROLE_TRACKARTIST] && *video.contributor[ROLE_TRACKARTIST] )
{
m.artist = escape_tag(trim(video.contributor[ROLE_TRACKARTIST]), 1);
}
if( video.contributor[ROLE_ALBUMARTIST] && *video.contributor[ROLE_ALBUMARTIST] )
{
m.creator = escape_tag(trim(video.contributor[ROLE_ALBUMARTIST]), 1);
}
else
{
m.creator = m.artist;
free_flags &= ~FLAG_CREATOR;
}
}
}
#ifndef NETGEAR
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 )
{
if( ctx->metadata )
{
AVDictionaryEntry *tag = NULL;
//DEBUG DPRINTF(E_DEBUG, L_METADATA, "Metadata:\n");
while( (tag = av_dict_get(ctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)) )
{
//DEBUG DPRINTF(E_DEBUG, L_METADATA, " %-16s: %s\n", tag->key, tag->value);
if( strcmp(tag->key, "title") == 0 )
m.title = escape_tag(trim(tag->value), 1);
else if( strcmp(tag->key, "genre") == 0 )
m.genre = escape_tag(trim(tag->value), 1);
else if( strcmp(tag->key, "artist") == 0 )
m.artist = escape_tag(trim(tag->value), 1);
else if( strcmp(tag->key, "comment") == 0 )
m.comment = escape_tag(trim(tag->value), 1);
}
}
}
#endif
#endif
video_no_dlna:
#ifdef TIVO_SUPPORT
if( ends_with(path, ".TiVo") && is_tivo_file(path) )
{
if( m.dlna_pn )
{
free(m.dlna_pn);
m.dlna_pn = NULL;
}
m.mime = realloc(m.mime, 21);
strcpy(m.mime, "video/x-tivo-mpeg");
}
#endif
strcpy(nfo, path);
ext = strrchr(nfo, '.');
if( ext )
{
strcpy(ext+1, "nfo");
if( access(nfo, F_OK) == 0 )
{
parse_nfo(nfo, &m);
}
}
if( !m.mime )
{
if( strcmp(ctx->iformat->name, "avi") == 0 )
xasprintf(&m.mime, "video/x-msvideo");
else if( strncmp(ctx->iformat->name, "mpeg", 4) == 0 )
xasprintf(&m.mime, "video/mpeg");
else if( strcmp(ctx->iformat->name, "asf") == 0 )
xasprintf(&m.mime, "video/x-ms-wmv");
else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 )
if( ends_with(path, ".mov") )
xasprintf(&m.mime, "video/quicktime");
else
xasprintf(&m.mime, "video/mp4");
else if( strncmp(ctx->iformat->name, "matroska", 8) == 0 )
xasprintf(&m.mime, "video/x-matroska");
else if( strcmp(ctx->iformat->name, "flv") == 0 )
xasprintf(&m.mime, "video/x-flv");
else
DPRINTF(E_WARN, L_METADATA, "%s: Unhandled format: %s\n", path, ctx->iformat->name);
}
lav_close(ctx);
if( !m.date )
{
m.date = malloc(20);
modtime = localtime(&file.st_mtime);
strftime(m.date, 20, "%FT%T", modtime);
}
if( !m.title )
m.title = strdup(name);
album_art = find_album_art(path, video.image, video.image_size);
freetags(&video);
ret = sql_exec(db, "INSERT into DETAILS"
" (PATH, SIZE, TIMESTAMP, DURATION, DATE, CHANNELS, BITRATE, SAMPLERATE, RESOLUTION,"
" TITLE, CREATOR, ARTIST, GENRE, COMMENT, DLNA_PN, MIME, ALBUM_ART) "
"VALUES"
" (%Q, %lld, %ld, %Q, %Q, %Q, %Q, %Q, %Q, '%q', %Q, %Q, %Q, %Q, %Q, '%q', %lld);",
path, (long long)file.st_size, file.st_mtime, m.duration,
m.date, m.channels, m.bitrate, m.frequency, m.resolution,
m.title, m.creator, m.artist, m.genre, m.comment, m.dlna_pn,
m.mime, album_art);
if( ret != SQLITE_OK )
{
fprintf(stderr, "Error inserting details for '%s'!\n", path);
ret = 0;
}
else
{
ret = sqlite3_last_insert_rowid(db);
check_for_captions(path, ret);
}
free_metadata(&m, free_flags);
free(path_cpy);
return ret;
}
//
// tagutils-dsf.c
//
//
// Created by inXtron,Inc on 13/10/3.
//
//
static int
_get_dsftags(char *file, struct song_metadata *psong)
{
FILE *infile;
FILE *outfile;
char *buffer = NULL;
unsigned long long id3TagPoint = 0;
buffer = (char *)malloc(sizeof(char)*1024);
memset(buffer , 0, sizeof(buffer));
//DEBUG DPRINTF(E_DEBUG,L_SCANNER,"File : %s\n",file);
if(!(infile = fopen(file, "rb")))
{
DPRINTF(E_DEBUG, L_SCANNER, "Could not open %s for reading\n", file);
return -1;
}
outfile = fopen("/tmp/id3.tag", "wb");
// Find the ID3 Tag Position of File
fseek(infile, 20 , SEEK_SET);
fread(buffer, 1, 4, infile );
id3TagPoint = (buffer[0] & 0xffULL) | (buffer[1] & 0xffULL) << 8ULL | (buffer[2] & 0xffULL) << 16ULL | (buffer[3] & 0xffULL) << 24ULL| (buffer[4] & 0xffULL) << 32ULL | (buffer[5] & 0xffull) << 40ULL |(buffer[6] & 0xffULL) << 48ULL | (buffer[7] & 0xffULL) << 56ULL ;
//DEBUG DPRINTF(E_DEBUG,L_SCANNER,"id3TagPoint : %lld\n",id3TagPoint);
//DEBUG DPRINTF(E_DEBUG,L_SCANNER,"id3TagPoint : 0x%08x\n",id3TagPoint);
// Jump to the Position of ID3 Tag & read
if (id3TagPoint > 0) {
fseeko(infile, id3TagPoint , SEEK_SET);
memset(buffer , 0, sizeof(buffer));
//Dump out to /tmp/id3.tag
while (fread(buffer, 1, 1024, infile ) !=0 ){
fwrite(buffer, 1, 1024, outfile);
}
}
fclose(outfile);
fclose(infile);
// Read ID3 Tag
struct id3_file *pid3file;
struct id3_tag *pid3tag;
struct id3_frame *pid3frame;
int err;
int index;
int used;
unsigned char *utf8_text;
int genre = WINAMP_GENRE_UNKNOWN;
int have_utf8;
int have_text;
id3_ucs4_t const *native_text;
char *tmp;
int got_numeric_genre;
id3_byte_t const *image;
id3_length_t image_size = 0;
pid3file = id3_file_open("/tmp/id3.tag", ID3_FILE_MODE_READONLY);
if(!pid3file)
{
DPRINTF(E_ERROR, L_SCANNER, "Cannot open %s\n", file);
return -1;
}
pid3tag = id3_file_tag(pid3file);
if(!pid3tag)
{
err = errno;
id3_file_close(pid3file);
errno = err;
DPRINTF(E_WARN, L_SCANNER, "Cannot get ID3 tag for %s\n", file);
return -1;
}
index = 0;
while((pid3frame = id3_tag_findframe(pid3tag, "", index)))
{
used = 0;
utf8_text = NULL;
native_text = NULL;
have_utf8 = 0;
have_text = 0;
if(!strcmp(pid3frame->id, "YTCP")) /* for id3v2.2 */
{
psong->compilation = 1;
DPRINTF(E_DEBUG, L_SCANNER, "Compilation: %d [%s]\n", psong->compilation, basename(file));
}
else if(!strcmp(pid3frame->id, "APIC") && !image_size)
{
if( (strcmp((char*)id3_field_getlatin1(&pid3frame->fields[1]), "image/jpeg") == 0) ||
(strcmp((char*)id3_field_getlatin1(&pid3frame->fields[1]), "image/jpg") == 0) ||
(strcmp((char*)id3_field_getlatin1(&pid3frame->fields[1]), "jpeg") == 0) )
{
image = id3_field_getbinarydata(&pid3frame->fields[4], &image_size);
if( image_size )
{
psong->image = malloc(image_size);
memcpy(psong->image, image, image_size);
psong->image_size = image_size;
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Found thumbnail: %d\n", psong->image_size);
}
}
}
if(((pid3frame->id[0] == 'T') || (strcmp(pid3frame->id, "COMM") == 0)) &&
(id3_field_getnstrings(&pid3frame->fields[1])))
have_text = 1;
if(have_text)
{
native_text = id3_field_getstrings(&pid3frame->fields[1], 0);
//DEBUG DPRINTF(E_DEBUG,L_SCANNER,"native_text %s\n",(char*)native_text);
if(native_text)
{
have_utf8 = 1;
if(lang_index >= 0)
utf8_text = _get_utf8_text(native_text); // through iconv
else
utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "%s %s\n",pid3frame->id, (char*)utf8_text);
if(!strcmp(pid3frame->id, "TIT2"))
{
used = 1;
psong->title = (char*)utf8_text;
}
else if(!strcmp(pid3frame->id, "TPE1"))
{
used = 1;
psong->contributor[ROLE_ARTIST] = (char*)utf8_text;
}
else if(!strcmp(pid3frame->id, "TALB"))
{
used = 1;
psong->album = (char*)utf8_text;
}
else if(!strcmp(pid3frame->id, "TCOM"))
{
used = 1;
psong->contributor[ROLE_COMPOSER] = (char*)utf8_text;
}
else if(!strcmp(pid3frame->id, "TIT1"))
{
used = 1;
psong->grouping = (char*)utf8_text;
}
else if(!strcmp(pid3frame->id, "TPE2"))
{
used = 1;
psong->contributor[ROLE_BAND] = (char*)utf8_text;
}
else if(!strcmp(pid3frame->id, "TPE3"))
{
used = 1;
psong->contributor[ROLE_CONDUCTOR] = (char*)utf8_text;
}
else if(!strcmp(pid3frame->id, "TCON"))
{
used = 1;
psong->genre = (char*)utf8_text;
got_numeric_genre = 0;
if(psong->genre)
{
if(!strlen(psong->genre))
{
genre = WINAMP_GENRE_UNKNOWN;
got_numeric_genre = 1;
}
else if(isdigit(psong->genre[0]))
{
genre = atoi(psong->genre);
got_numeric_genre = 1;
}
else if((psong->genre[0] == '(') && (isdigit(psong->genre[1])))
{
genre = atoi((char*)&psong->genre[1]);
got_numeric_genre = 1;
}
if(got_numeric_genre)
{
if((genre < 0) || (genre > WINAMP_GENRE_UNKNOWN))
genre = WINAMP_GENRE_UNKNOWN;
free(psong->genre);
psong->genre = strdup(winamp_genre[genre]);
}
}
}
else if(!strcmp(pid3frame->id, "COMM"))
{
used = 1;
psong->comment = (char*)utf8_text;
}
else if(!strcmp(pid3frame->id, "TPOS"))
{
tmp = (char*)utf8_text;
strsep(&tmp, "/");
if(tmp)
{
psong->total_discs = atoi(tmp);
}
psong->disc = atoi((char*)utf8_text);
}
else if(!strcmp(pid3frame->id, "TRCK"))
{
tmp = (char*)utf8_text;
strsep(&tmp, "/");
if(tmp)
{
psong->total_tracks = atoi(tmp);
}
psong->track = atoi((char*)utf8_text);
}
else if(!strcmp(pid3frame->id, "TDRC"))
{
#if 1 // New Version 20131209 // 2013-08-05T22:40
if (strrchr((char*)utf8_text, 'T') > 0)
psong->time = atoi((char*)strrchr((char*)utf8_text, 'T')+1)*100 + atoi((char*)strrchr((char*)utf8_text, ':')+1);
if ((char*)strchr((char*)utf8_text, '-') > 0)
psong->date = atoi((char*)strchr((char*)utf8_text, '-')+1)*100 + atoi((char*)strrchr((char*)utf8_text, '-')+1);
psong->year = atoi((char*)utf8_text);
// DPRINTF(E_DEBUG, L_SCANNER, "TDRC => %04d,%04d,%04d\n", psong->year, psong->date, psong->time);
#else // Old Version
psong->year = atoi((char*)utf8_text);
#endif
}
else if(!strcmp(pid3frame->id, "TLEN"))
{
psong->song_length = atoi((char*)utf8_text);
}
else if(!strcmp(pid3frame->id, "TBPM"))
{
psong->bpm = atoi((char*)utf8_text);
}
else if(!strcmp(pid3frame->id, "TCMP"))
{
psong->compilation = (char)atoi((char*)utf8_text);
}
}
}
// check if text tag
if((!used) && (have_utf8) && (utf8_text))
free(utf8_text);
// v2 COMM
if((!strcmp(pid3frame->id, "COMM")) && (pid3frame->nfields == 4))
{
native_text = id3_field_getstring(&pid3frame->fields[2]);
if(native_text)
{
utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text);
if((utf8_text) && (strncasecmp((char*)utf8_text, "iTun", 4) != 0))
{
// read comment
free(utf8_text);
native_text = id3_field_getfullstring(&pid3frame->fields[3]);
if(native_text)
{
utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text);
if(utf8_text)
{
free(psong->comment);
psong->comment = (char*)utf8_text;
}
}
}
else
{
free(utf8_text);
}
}
}
index++;
}
free(buffer);
id3_file_close(pid3file);
remove("/tmp/id3.tag");
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Got id3 tag successfully for file=%s\n", file);
return 0;
}
static int _get_dsffileinfo(char *file, struct song_metadata *psong)
{
FILE * infile;
char *buffer = NULL;
unsigned long long FileSize=0;
// int FileSize_H=0;
// int ChannelType = 0;
int ChannelNum = 0;
unsigned long long SongSize = 0;
// int SongSize_H = 0;
long length = 0 ;
long SampleFreq = 0;
buffer = (char *)malloc(sizeof(char)*1024);
memset(buffer , 0, sizeof(buffer));
// if (strstr(file,"._*")>=0) return -1;
if (!(infile = fopen(file, "rb")))
{
DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file);
return -1;
}
fread(buffer, 1, 28, infile);
FileSize = (buffer[12]&0xffULL)|((buffer[13]&0xffULL)<< 8ULL )|((buffer[14]&0xffULL)<< 16ULL)|((buffer[15]&0xffULL)<<24ULL)|(buffer[16]&0xffULL)<<32ULL|((buffer[17]&0xffULL)<< 40ULL )|((buffer[18]&0xffULL)<< 48ULL)|((buffer[19]&0xffULL)<<56ULL);
// FileSize_H = (buffer[16]&0xff)|((buffer[17]&0xff)<< 8 )|((buffer[18]&0xff)<< 16)|((buffer[19]&0xff)<<24);
fseek(infile, 28, SEEK_SET);
// Read FMT Chunk
fread(buffer, 1, 36, infile);
// ChannelType = (buffer[20]&0xff);
ChannelNum = (buffer[24]&0xff);
SampleFreq = (buffer[28]&0xff)|((buffer[29]&0xff)<< 8 )|((buffer[30]&0xff)<< 16)|((buffer[31]&0xff)<<24);
fseek(infile, 60, SEEK_SET);
fread(buffer, 1, 12, infile);
SongSize = (buffer[4]&0xffULL)|((buffer[5]&0xffULL)<< 8ULL )|((buffer[6]&0xffULL)<< 16ULL)|((buffer[7]&0xffULL)<< 24ULL)|(buffer[8]&0xffULL)<< 32ULL|((buffer[9]&0xffULL)<< 40ULL )|((buffer[10]&0xffULL)<< 48ULL)|((buffer[11]&0xffULL)<<56ULL);
// SongSize_H = (buffer[8]&0xff)|((buffer[9]&0xff)<< 8 )|((buffer[10]&0xff)<< 16)|((buffer[11]&0xff)<<24);
length = SongSize / SampleFreq;
// DPRINTF(E_DEBUG, L_SCANNER, "FileSize %d\n", FileSize);
// DPRINTF(E_DEBUG, L_SCANNER, "ChannelType %d\n", ChannelType);
// DPRINTF(E_DEBUG, L_SCANNER, "ChannelNum %d\n", ChannelNum);
// DPRINTF(E_DEBUG, L_SCANNER, "SampleFreq %d\n", SampleFreq);
// DPRINTF(E_DEBUG, L_SCANNER, "SongSize %d\n", SongSize);
// DPRINTF(E_DEBUG, L_SCANNER, "length %d\n", length);
psong->file_size = FileSize;
psong->channels = ChannelNum;
psong->samplerate = SampleFreq ;
// psong->bitrate = SampleFreq ;
psong->song_length= length * 1000 ;
// DPRINTF(E_DEBUG, L_SCANNER, "FileSize %d\n", psong->file_size);
// DPRINTF(E_DEBUG, L_SCANNER, "ChannelNum %d\n", psong->channels);
// DPRINTF(E_DEBUG, L_SCANNER, "SampleFreq %d\n", psong->samplerate);
// DPRINTF(E_DEBUG, L_SCANNER, "length %d\n", psong->song_length);
fclose(infile);
free(buffer);
// xasprintf(&(psong->dlna_pn), "DSD"); // Disable on Beta VI 20131230
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.