Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[db,ffmpeg] calculate and persist audio hash on local files #1622

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions owntone.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ library {
# the playlist like MPD does. Note that some dacp clients do not show
# the playqueue if playback is stopped.
# clear_queue_on_stop_disable = false

# Enable audio hash persistance for local files
# Audio streams of files can be hashed to give identifiers to audio files
# that are independant of any metadata, but generation is a more expensive
# operation
# audio_hash = false
}

# Local audio output
Expand Down
1 change: 1 addition & 0 deletions src/conffile.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ static cfg_opt_t sec_library[] =
CFG_BOOL("only_first_genre", cfg_false, CFGF_NONE),
CFG_STR_LIST("decode_audio_filters", NULL, CFGF_NONE),
CFG_STR_LIST("decode_video_filters", NULL, CFGF_NONE),
CFG_BOOL("audio_hash", cfg_false, CFGF_NONE),
CFG_END()
};

Expand Down
3 changes: 3 additions & 0 deletions src/db.c
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ static const struct col_type_map mfi_cols_map[] =
{ "channels", mfi_offsetof(channels), DB_TYPE_INT },
{ "usermark", mfi_offsetof(usermark), DB_TYPE_INT },
{ "scan_kind", mfi_offsetof(scan_kind), DB_TYPE_INT },
{ "audio_hash", mfi_offsetof(audio_hash), DB_TYPE_STRING },
};

/* This list must be kept in sync with
Expand Down Expand Up @@ -371,6 +372,7 @@ static const ssize_t dbmfi_cols_map[] =
dbmfi_offsetof(channels),
dbmfi_offsetof(usermark),
dbmfi_offsetof(scan_kind),
dbmfi_offsetof(audio_hash),
};

/* This list must be kept in sync with
Expand Down Expand Up @@ -777,6 +779,7 @@ free_mfi(struct media_file_info *mfi, int content_only)
free(mfi->composer_sort);
free(mfi->album_artist_sort);
free(mfi->virtual_path);
free(mfi->audio_hash);

if (!content_only)
free(mfi);
Expand Down
3 changes: 3 additions & 0 deletions src/db.h
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ struct media_file_info {
char *composer_sort;

uint32_t scan_kind; /* Identifies the library_source that created/updates this item */

char *audio_hash; /* sha256 - ffmpeg -i foo.mp3 -c:a copy -bsf:a null -f hash - */
};

#define mfi_offsetof(field) offsetof(struct media_file_info, field)
Expand Down Expand Up @@ -422,6 +424,7 @@ struct db_media_file_info {
char *channels;
char *usermark;
char *scan_kind;
char *audio_hash;
};

#define dbmfi_offsetof(field) offsetof(struct db_media_file_info, field)
Expand Down
3 changes: 2 additions & 1 deletion src/db_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@
" composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" channels INTEGER DEFAULT 0," \
" usermark INTEGER DEFAULT 0," \
" scan_kind INTEGER DEFAULT 0" \
" scan_kind INTEGER DEFAULT 0," \
" audio_hash VARCHAR(128) DEFAULT NULL" \
");"

#define T_PL \
Expand Down
2 changes: 1 addition & 1 deletion src/db_init.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* is a major upgrade. In other words minor version upgrades permit downgrading
* the server after the database was upgraded. */
#define SCHEMA_VERSION_MAJOR 22
#define SCHEMA_VERSION_MINOR 0
#define SCHEMA_VERSION_MINOR 1

int
db_init_indices(sqlite3 *hdl);
Expand Down
21 changes: 21 additions & 0 deletions src/db_upgrade.c
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,21 @@ static const struct db_upgrade_query db_upgrade_v2200_queries[] =
{ U_v2200_SCVER_MINOR, "set schema_version_minor to 00" },
};

/* ---------------------------- 22.00 -> 22.01 ------------------------------ */
#define U_v2201_ALTER_FILES_AUDIOHASH \
"ALTER TABLE files ADD COLUMN audio_hash VARCHAR(128) DEFAULT NULL;"
#define U_v2201_SCVER_MINOR \
"UPDATE admin SET value = '01' WHERE key = 'schema_version_minor';"

static const struct db_upgrade_query db_upgrade_v2201_queries[] =
{
{ U_v2201_ALTER_FILES_AUDIOHASH, "update files adding audio_hash" },

{ U_v2201_SCVER_MINOR, "set schema_version_minor to 01" },
};



/* -------------------------- Main upgrade handler -------------------------- */

int
Expand Down Expand Up @@ -1437,6 +1452,12 @@ db_upgrade(sqlite3 *hdl, int db_ver)
if (ret < 0)
return -1;

/* FALLTHROUGH */

case 2200:
ret = db_generic_upgrade(hdl, db_upgrade_v2201_queries, ARRAY_SIZE(db_upgrade_v2201_queries));
if (ret < 0)
return -1;

/* Last case statement is the only one that ends with a break statement! */
break;
Expand Down
2 changes: 2 additions & 0 deletions src/httpd_jsonapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ track_to_json(struct db_media_file_info *dbmfi)
safe_json_add_int_from_string(item, "channels", dbmfi->channels);
safe_json_add_int_from_string(item, "usermark", dbmfi->usermark);

safe_json_add_string(item, "audio_hash", dbmfi->audio_hash);

ret = safe_atoi32(dbmfi->media_kind, &intval);
if (ret == 0)
safe_json_add_string(item, "media_kind", db_media_kind_label(intval));
Expand Down
52 changes: 52 additions & 0 deletions src/library/filescanner_ffmpeg.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/hash.h>
#include <libavutil/opt.h>

#include "db.h"
Expand Down Expand Up @@ -373,6 +374,53 @@ extract_metadata(struct media_file_info *mfi, AVFormatContext *ctx, AVStream *au
return mdcount;
}

static int
calc_audio_hash(struct media_file_info *mfi, AVFormatContext *ctx, const int audio_stream_idx)
{
struct AVPacket *pkt = NULL;
struct AVHashContext *hash = NULL;
char res[2 * AV_HASH_MAX_SIZE + 4] = { 0 };
int ret = -1;

if (!cfg_getbool(cfg_getsec(cfg, "library"), "audio_hash"))
return 0;


pkt = av_packet_alloc();

ret = av_hash_alloc(&hash, "sha256");
if (ret < 0)
{
DPRINTF(E_WARN, L_SCAN, "Failed to find sha256 hash - %s\n", ret == EINVAL ? "unknown hash" : strerror(ret));
goto cleanup;
}

av_hash_init(hash);

while ((ret = av_read_frame(ctx, pkt)) >= 0)
{
if (pkt->stream_index != audio_stream_idx) {
av_packet_unref(pkt);
continue;
}

av_hash_update(hash, pkt->data, pkt->size);
av_packet_unref(pkt);
}

av_hash_final_hex(hash, (uint8_t*)res, sizeof(res));

mfi->audio_hash = strdup(res);

ret = 0;

cleanup:
if (pkt) av_packet_free(&pkt);
if (hash) av_hash_freep(&hash);

return ret;
}

/*
* Fills metadata read with ffmpeg/libav from the given path into the given mfi
*
Expand Down Expand Up @@ -513,6 +561,10 @@ scan_metadata_ffmpeg(struct media_file_info *mfi, const char *file)
audio_stream = ctx->streams[i];
audio_codec_id = codec_id;

/* only scan for files because we need to read the entire audio */
if (mfi->data_kind == DATA_KIND_FILE)
calc_audio_hash(mfi, ctx, i);

mfi->samplerate = sample_rate;
mfi->bits_per_sample = 8 * av_get_bytes_per_sample(sample_fmt);
if (mfi->bits_per_sample == 0)
Expand Down
1 change: 1 addition & 0 deletions src/parsers/smartpl_lexer.l
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ samplerate { yylval->str = strdup(yytext); return SMARTPL_T_INTTAG; }
song_length { yylval->str = strdup(yytext); return SMARTPL_T_INTTAG; }
usermark { yylval->str = strdup(yytext); return SMARTPL_T_INTTAG; }
file_size { yylval->str = strdup(yytext); return SMARTPL_T_INTTAG; }
audio_hash { yylval->str = strdup(yytext); return SMARTPL_T_STRTAG; }

time_added { yylval->str = strdup(yytext); return SMARTPL_T_DATETAG; }
time_modified { yylval->str = strdup(yytext); return SMARTPL_T_DATETAG; }
Expand Down