diff --git a/src/filescanner_ffmpeg.c b/src/filescanner_ffmpeg.c new file mode 100644 index 00000000..c16fec80 --- /dev/null +++ b/src/filescanner_ffmpeg.c @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2009 Julien BLACHE + * + * This program 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 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include + +#ifdef HAVE_STDINT_H +#include +#endif + +#include +#include + +#include +#include + +#include "daapd.h" +#include "err.h" +#include "mp3-scanner.h" + + +/* Mapping between the metadata name(s) and the offset + * of the equivalent metadata field in struct media_file_info */ +struct metadata_map { + char *key; + int as_int; + size_t offset; +}; + +#define mfi_offsetof(field) offsetof(struct media_file_info, field) + +/* Lookup is case-insensitive, first occurrence takes precedence */ +static struct metadata_map md_map[] = + { + { "title", 0, mfi_offsetof(title) }, + { "artist", 0, mfi_offsetof(artist) }, + { "author", 0, mfi_offsetof(artist) }, + { "albumartist", 0, mfi_offsetof(album_artist) }, + { "album", 0, mfi_offsetof(album) }, + { "genre", 0, mfi_offsetof(genre) }, + { "composer", 0, mfi_offsetof(composer) }, + { "grouping", 0, mfi_offsetof(grouping) }, + { "orchestra", 0, mfi_offsetof(orchestra) }, + { "conductor", 0, mfi_offsetof(conductor) }, + { "comment", 0, mfi_offsetof(comment) }, + { "description", 0, mfi_offsetof(comment) }, + { "totaltracks", 1, mfi_offsetof(total_tracks) }, + { "track", 1, mfi_offsetof(track) }, + { "tracknumber", 1, mfi_offsetof(track) }, + { "totaldiscs", 1, mfi_offsetof(total_discs) }, + { "disc", 1, mfi_offsetof(disc) }, + { "discnumber", 1, mfi_offsetof(disc) }, + { "year", 1, mfi_offsetof(year) }, + { "date", 1, mfi_offsetof(year) }, + { NULL, 0, 0 } + }; + + +int scan_get_ffmpeginfo(char *filename, struct media_file_info *mfi) +{ + AVFormatContext *ctx; + AVMetadataTag *mdt; + enum CodecID codec_id; + enum CodecID video_codec_id; + enum CodecID audio_codec_id; + int video_stream; + int audio_stream; + char **strval; + uint32_t *intval; + char *endptr; + long tmpval; + int mdcount; + int i; + int ret; + + ret = av_open_input_file(&ctx, filename, NULL, 0, NULL); + if (ret != 0) + { + DPRINTF(E_WARN, L_SCAN, "Cannot open media file '%s': %s\n", filename, strerror(AVUNERROR(ret))); + + return FALSE; + } + + ret = av_find_stream_info(ctx); + if (ret < 0) + { + DPRINTF(E_WARN, L_SCAN, "Cannot get stream info: %s\n", strerror(AVUNERROR(ret))); + + av_close_input_file(ctx); + return FALSE; + } + +#if 0 + /* Dump input format as determined by ffmpeg */ + dump_format(ctx, 0, filename, FALSE); +#endif + + DPRINTF(E_DBG, L_SCAN, "File has %d streams\n", ctx->nb_streams); + + /* Extract codec IDs, check for video */ + video_codec_id = CODEC_ID_NONE; + video_stream = -1; + + audio_codec_id = CODEC_ID_NONE; + audio_stream = -1; + + for (i = 0; i < ctx->nb_streams; i++) + { + switch (ctx->streams[i]->codec->codec_type) + { + case CODEC_TYPE_VIDEO: + if (video_stream == -1) + { + DPRINTF(E_DBG, L_SCAN, "File has video (stream %d)\n", i); + + mfi->has_video = 1; + video_codec_id = ctx->streams[i]->codec->codec_id; + video_stream = i; + } + break; + + case CODEC_TYPE_AUDIO: + if (audio_stream == -1) + { + audio_codec_id = ctx->streams[i]->codec->codec_id; + audio_stream = i; + } + break; + + default: + break; + } + } + + /* Common media information */ + if (ctx->duration > 0) + mfi->song_length = ctx->duration / (AV_TIME_BASE / 1000); /* ms */ + + if (ctx->bit_rate > 0) + mfi->bitrate = ctx->bit_rate / 1000; + else if (ctx->duration > 0) /* guesstimate */ + mfi->bitrate = ((mfi->file_size * 8) / (ctx->duration / AV_TIME_BASE)) / 1000; + + DPRINTF(E_DBG, L_SCAN, "Duration %d ms, bitrate %d kbps\n", mfi->song_length, mfi->bitrate); + + /* Get some more information on the audio stream */ + if (audio_stream != -1) + { + if (ctx->streams[audio_stream]->codec->sample_rate != 0) + mfi->samplerate = ctx->streams[audio_stream]->codec->sample_rate; + + /* Try sample format first */ + mfi->bits_per_sample = av_get_bits_per_sample_format(ctx->streams[audio_stream]->codec->sample_fmt); + if (mfi->bits_per_sample == 0) + { + /* Try codec */ + mfi->bits_per_sample = av_get_bits_per_sample(audio_codec_id); + } + + DPRINTF(E_DBG, L_SCAN, "samplerate %d, bps %d\n", mfi->samplerate, mfi->bits_per_sample); + } + + /* Check codec */ + codec_id = (mfi->has_video) ? video_codec_id : audio_codec_id; + switch (codec_id) + { + case CODEC_ID_AAC: + DPRINTF(E_DBG, L_SCAN, "AAC\n"); + mfi->type = strdup("m4a"); + mfi->codectype = strdup("mp4a"); + mfi->description = strdup("AAC audio file"); + break; + + case CODEC_ID_ALAC: + DPRINTF(E_DBG, L_SCAN, "ALAC\n"); + mfi->type = strdup("m4a"); + mfi->codectype = strdup("alac"); + mfi->description = strdup("AAC audio file"); + break; + + case CODEC_ID_FLAC: + DPRINTF(E_DBG, L_SCAN, "FLAC\n"); + mfi->type = strdup("flac"); + mfi->codectype = strdup("flac"); + mfi->description = strdup("FLAC audio file"); + break; + + case CODEC_ID_MUSEPACK7: + case CODEC_ID_MUSEPACK8: + DPRINTF(E_DBG, L_SCAN, "Musepack\n"); + mfi->type = strdup("mpc"); + mfi->codectype = strdup("mpc"); + mfi->description = strdup("Musepack audio file"); + break; + + case CODEC_ID_MPEG4: /* Video */ + case CODEC_ID_H264: + DPRINTF(E_DBG, L_SCAN, "MPEG4 video\n"); + mfi->type = strdup("m4v"); + mfi->codectype = strdup("mp4v"); + mfi->description = strdup("MPEG-4 video file"); + break; + + case CODEC_ID_MP3: + DPRINTF(E_DBG, L_SCAN, "MP3\n"); + mfi->type = strdup("mp3"); + mfi->codectype = strdup("mpeg"); + mfi->description = strdup("MPEG audio file"); + break; + + case CODEC_ID_VORBIS: + DPRINTF(E_DBG, L_SCAN, "VORBIS\n"); + mfi->type = strdup("ogg"); + mfi->codectype = strdup("ogg"); + mfi->description = strdup("Ogg Vorbis audio file"); + break; + + case CODEC_ID_WMAVOICE: + DPRINTF(E_DBG, L_SCAN, "WMA Voice\n"); + mfi->type = strdup("wma"); + mfi->codectype = strdup("wmav"); + mfi->description = strdup("WMA audio file"); + break; + + case CODEC_ID_WMAPRO: + DPRINTF(E_DBG, L_SCAN, "WMA Pro\n"); + mfi->type = strdup("wmap"); + mfi->codectype = strdup("wma"); + mfi->description = strdup("WMA audio file"); + break; + + case CODEC_ID_WMALOSSLESS: + DPRINTF(E_DBG, L_SCAN, "WMA Lossless\n"); + mfi->type = strdup("wma"); + mfi->codectype = strdup("wmal"); + mfi->description = strdup("WMA audio file"); + break; + + case CODEC_ID_PCM_S16LE ... CODEC_ID_PCM_F64LE: + if (strcmp(ctx->iformat->name, "aiff") == 0) + { + DPRINTF(E_DBG, L_SCAN, "AIFF\n"); + mfi->type = strdup("aif"); + mfi->codectype = strdup("aif"); + mfi->description = strdup("AIFF audio file"); + break; + } + else if (strcmp(ctx->iformat->name, "wav") == 0) + { + DPRINTF(E_DBG, L_SCAN, "WAV\n"); + mfi->type = strdup("wav"); + mfi->codectype = strdup("wav"); + mfi->description = strdup("WAV audio file"); + break; + } + /* WARNING: will fallthrough to default case, don't move */ + /* FALLTHROUGH */ + + default: + DPRINTF(E_DBG, L_SCAN, "Unknown codec 0x%x (video: %s), format %s (%s)\n", + codec_id, (mfi->has_video) ? "yes" : "no", ctx->iformat->name, ctx->iformat->long_name); + mfi->type = strdup("unkn"); + mfi->codectype = strdup("unkn"); + if (mfi->has_video) + mfi->description = strdup("Unknown video file format"); + else + mfi->description = strdup("Unknown audio file format"); + break; + } + + mdcount = 0; + + if (ctx->metadata == NULL) + { + DPRINTF(E_WARN, L_SCAN, "ffmpeg reports no metadata\n"); + + goto skip_extract; + } + +#if 0 + /* Dump all the metadata reported by ffmpeg */ + mdt = NULL; + while ((mdt = av_metadata_get(ctx->metadata, "", mdt, AV_METADATA_IGNORE_SUFFIX)) != NULL) + fprintf(stderr, " -> %s = %s\n", mdt->key, mdt->value); +#endif + + /* Extract actual metadata */ + for (i = 0; md_map[i].key != NULL; i++) + { + mdt = av_metadata_get(ctx->metadata, md_map[i].key, NULL, 0); + if (mdt == NULL) + continue; + + if ((mdt->value == NULL) || (strlen(mdt->value) == 0)) + continue; + + mdcount++; + + if (!md_map[i].as_int) + { + strval = (char **) ((char *) mfi + md_map[i].offset); + + if (*strval == NULL) + *strval = strdup(mdt->value); + } + else + { + intval = (uint32_t *) ((char *) mfi + md_map[i].offset); + + if (*intval == 0) + { + errno = 0; + tmpval = strtol(mdt->value, &endptr, 10); + + if (((errno == ERANGE) && ((tmpval == LONG_MAX) || (tmpval == LONG_MIN))) + || ((errno != 0) && (tmpval == 0))) + continue; + + if (endptr == mdt->value) + continue; + + if (tmpval > UINT32_MAX) + continue; + + *intval = (uint32_t) tmpval; + } + } + } + + skip_extract: + if (mdcount == 0) + DPRINTF(E_WARN, L_SCAN, "Could not extract any metadata\n"); + + /* Just in case there's no title set ... */ + if (mfi->title == NULL) + mfi->title = strdup(mfi->fname); + + /* All done */ + av_close_input_file(ctx); + + return TRUE; +}