From e43caee1576bcd30a200ccebb9cbc90c2b29bcc2 Mon Sep 17 00:00:00 2001 From: Ron Pedde Date: Sun, 12 Sep 2004 23:20:29 +0000 Subject: [PATCH] Add support for smart playlist manipulation by date added to database. Updated mt-daapd.playlist to match --- ChangeLog | 4 +++ configure.in | 2 +- contrib/mt-daapd.playlist | 70 ++++++++++++++++++++++++++++++++------- src/db-gdbm.c | 6 ++-- src/lexer.l | 50 ++++++++++++++++++++++++++++ src/main.c | 3 +- src/mp3-scanner.c | 15 ++++++++- src/parser.y | 45 +++++++++++++++++++++++++ src/playlist.c | 37 ++++++++++++++++++++- src/playlist.h | 1 + 10 files changed, 215 insertions(+), 18 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7eaa4de9..303b10c3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2204-09-08 Ron Pedde + * mp3-scanner.c: decode more aac tags. Feature request #990546 + * mp3-scanner.c: support for bitrate on playlsits. FR #996506 + 2004-05-21 Ron Pedde * everywhere: fixes for more conformant protocol handling, support for index tag, meta tag, bug fixes for configuration file diff --git a/configure.in b/configure.in index 96079489..7b31f149 100644 --- a/configure.in +++ b/configure.in @@ -4,7 +4,7 @@ dnl Process this file with autoconf to produce a configure script. AC_INIT(config.h.in) AM_CONFIG_HEADER(config.h) -AM_INIT_AUTOMAKE(mt-daapd,0.2.0) +AM_INIT_AUTOMAKE(mt-daapd,0.2.1-cvs) dnl Checks for programs. AC_PROG_CC diff --git a/contrib/mt-daapd.playlist b/contrib/mt-daapd.playlist index 27b140af..ab38ead1 100644 --- a/contrib/mt-daapd.playlist +++ b/contrib/mt-daapd.playlist @@ -20,13 +20,16 @@ # Orchestra (string) # Conductor (string) # Grouping (string) -- I don't even know what this is... +# Comment (string) # Year (int) # BPM (int) # Bitrate (int) +# Date (date) # # Valid operators include: # is, includes (string) # >, <, <=, >=, = (int) +# after, before (date) # # the "is" operator must exactly match the tag, # while the "includes" operator matches a substring. @@ -69,6 +72,52 @@ # orchestra or conductor... this would probably include any # orchestral music. Kind of ugly, but works! # +# +# DATES +# +# Dates are kind of funky. The "date" of a file is when it +# was created on the file system, or the date that it was first +# entered into the database, whichever is earlier. The date of +# a file can be matched with the terms "before" or "after". +# +# One example of a valid date is a date in yyyy-mm-dd format: +# +# "Files added after January 1, 2004" { +# date after 2004-01-01 +# } +# +# There are also some special date keywords: +# "today", "yesterday", "last week", "last month", "last year" +# +# A valid date can also be made by appling an interval to a +# date. As an example, a valid date might be: +# +# 3 weeks before today +# or +# 3 weeks ago +# +# You can combine these, of course. +# +# "3 weeks before today" is the same as "2 weeks before last week" +# or "1 week after last month" or "21 days before today" or +# "20 days before yesterday" or "7 days after last month". You get +# the idea. +# +# Note that the playlists are only generated at the time that mt-daapd +# starts... so while the dates will be accurate at start time, they +# may become inaccurate with time. Yes, I know... it's on my list. :) +# +# So, examples: +# +# "Recently Added MP3s" { +# date after last month AND file includes ".mp3" +# } +# +# This matches only mp3 files added in the last 30 days. +# +# +# SUMMARY +# # I expect that this language will grow over time. If you want # to hack on it, see src/lexer.l, src/parser.y, and src/playlist.c # @@ -77,22 +126,19 @@ # "60's Music" { - Year > 1959 && Year < 1970 + Year >= 1960 && Year < 1970 } -"70's Music" { - Year > 1969 && Year < 1980 +"Recently Added" { + Date after 2 weeks ago } -"80's Music" { - Year > 1979 && Year < 1990 +"Non-DRMed Music" { + path not includes ".m4p" } -"90's Music" { - Year > 1989 && Year < 2000 +"AAC Files" { + path includes ".m4p" || + path includes ".m4a" || + path includes ".aac" } - -"00's Music" { - Year > 1999 -} - diff --git a/src/db-gdbm.c b/src/db-gdbm.c index 921ff8cd..ec24ea9a 100644 --- a/src/db-gdbm.c +++ b/src/db-gdbm.c @@ -688,6 +688,7 @@ int db_unpackrecord(datum *pdatum, MP3FILE *pmp3) { pmp3->grouping=strdup(&ppacked->data[offset]); offset += ppacked->grouping_len; + /* shouldn't this have been done when scanning? */ make_composite_tags(pmp3); return 0; @@ -726,9 +727,10 @@ int db_add(MP3FILE *pmp3) { /* dummy this up in case the client didn't */ ppacked=(MP3PACKED *)pnew->dptr; - ppacked->time_added=(int)time(NULL); + if(!ppacked->time_added) + ppacked->time_added=(int)time(NULL); ppacked->time_modified=ppacked->time_added; - ppacked->time_played=0; + ppacked->time_played=0; /* do we want to keep track of this? */ if(gdbm_store(db_songs,dkey,*pnew,GDBM_REPLACE)) { DPRINTF(ERR_FATAL,"Error inserting file %s in database\n",pmp3->fname); diff --git a/src/lexer.l b/src/lexer.l index 59b43d6c..3dd206b8 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -23,12 +23,15 @@ #include #include #include +#include #include "err.h" #include "playlist.h" #include "parser.h" extern int yydebug; +time_t l_converttime(int day, int month, int year); +time_t l_convertyyyymmdd(char *date); %} @@ -36,6 +39,7 @@ extern int yydebug; %option case-insensitive qstring \"[^\"\n]*[\"\n] +yyyymmdd [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] %% @@ -55,6 +59,24 @@ year { yylval.ival=YEAR; return(YEAR); } bpm { yylval.ival=BPM; return(BPM); } bitrate { yylval.ival=BITRATE; return(BITRATE); } +date { yylval.ival=DATEADDED; return(DATEADDED); } + +{yyyymmdd} { yylval.ival=l_convertyyyymmdd(yytext); return(DATE); } +today { yylval.ival=time(NULL); return(DATE); } +yesterday { yylval.ival=time(NULL) - 24*3600; return(DATE); } +last\ week { yylval.ival=time(NULL) - 24*3600*7; return(DATE); } +last\ month { yylval.ival=time(NULL) - 24*3600*30; return(DATE); } +last\ year { yylval.ival=time(NULL) - 24*3600*365; return(DATE); } + +days? { yylval.ival=24*3600; return(INTERVAL); } +weeks? { yylval.ival=24*3600*7; return(INTERVAL); } +months? { yylval.ival=24*3600*30; return(INTERVAL); } +years? { yylval.ival=24*3600*365; return(INTERVAL); } + +ago { yylval.ival=AGO; return(AGO); } +before { yylval.ival=BEFORE; return(BEFORE); } +after { yylval.ival=AFTER; return(AFTER); } + is { yylval.ival=IS; return(IS); } includes { yylval.ival=INCLUDES; return(INCLUDES); } = { yylval.ival=EQUALS; return(EQUALS); } @@ -84,6 +106,34 @@ not | %% +time_t l_convertyyyymmdd(char *date) { + char year[5]; + char month[3]; + char day[3]; + + memset(year,0,sizeof(year)); + memset(month,0,sizeof(month)); + memset(day,0,sizeof(day)); + + strncpy(year,date,4); + strncpy(month,date+5,2); + strncpy(day,date+8,2); + + DPRINTF(ERR_INFO,"Converting %d-%d-%d\n",atoi(year),atoi(month),atoi(day)); + return l_converttime(atoi(day), atoi(month), atoi(year)); +} + +time_t l_converttime(int day, int month, int year) { + struct tm tm; + + memset((void*)&tm,0,sizeof(tm)); + tm.tm_year = year - 1900; + tm.tm_mon = month-1; + tm.tm_mday = day; + + return mktime(&tm); +} + int yywrap(void) { return 1; } diff --git a/src/main.c b/src/main.c index cb4573ab..5428f9c9 100644 --- a/src/main.c +++ b/src/main.c @@ -584,6 +584,7 @@ int main(int argc, char *argv[]) { case 'p': parseonly=1; + foreground=1; break; case 'r': @@ -607,7 +608,7 @@ int main(int argc, char *argv[]) { exit(EXIT_FAILURE); } - if(config.logfile) { + if((config.logfile) && (!parseonly)) { log_setdest(config.logfile,LOGDEST_LOGFILE); } else { if(!foreground) { diff --git a/src/mp3-scanner.c b/src/mp3-scanner.c index de0cd503..e5ad8b2f 100644 --- a/src/mp3-scanner.c +++ b/src/mp3-scanner.c @@ -472,13 +472,26 @@ void scan_music_file(char *path, struct dirent *pde, struct stat *psb) { /* FIXME; assumes that st_ino is a u_int_32 DWB: also assumes that the library is contained entirely within - one file system */ + one file system + REP: true, although linux doesn't even guarantee a unique inode + within a single device! + */ mp3file.id=psb->st_ino; /* Do the tag lookup here */ if(!scan_gettags(mp3file.path,&mp3file) && !scan_get_fileinfo(mp3file.path,&mp3file)) { make_composite_tags(&mp3file); + /* fill in the time_added. I'm not sure of the logic in this. + My thinking is to use time created, but what is that? Best + guess would be earliest of st_mtime and st_ctime... + */ + mp3file.time_added=psb->st_mtime; + if(psb->st_ctime < mp3file.time_added) + mp3file.time_added=psb->st_ctime; + + DPRINTF(ERR_DEBUG," Date Added: %d\n",mp3file.time_added); + db_add(&mp3file); pl_eval(&mp3file); /* FIXME: move to db_add? */ } else { diff --git a/src/parser.y b/src/parser.y index c1287f9e..7064308a 100644 --- a/src/parser.y +++ b/src/parser.y @@ -36,6 +36,7 @@ extern int yyerror(char *msg); extern PL_NODE *pl_newcharpredicate(int tag, int op, char *value); extern PL_NODE *pl_newintpredicate(int tag, int op, int value); +extern PL_NODE *pl_newdatepredicate(int tag, int op, int value); extern PL_NODE *pl_newexpr(PL_NODE *arg1, int op, PL_NODE *arg2); extern int pl_addplaylist(char *name, PL_NODE *root); @@ -76,17 +77,28 @@ int pl_number=2; %token ID %token NUM +%token DATE %token YEAR %token BPM %token BITRATE +%token DATEADDED +%token BEFORE +%token AFTER +%token AGO +%token INTERVAL + %type expression %type predicate %type strtag %type inttag +%type datetag +%type dateval +%type interval %type strbool %type intbool +%type datebool %type playlist %% @@ -106,6 +118,10 @@ expression: expression AND expression { $$=pl_newexpr($1,$2,$3); } predicate: strtag strbool ID { $$=pl_newcharpredicate($1, $2, $3); } | inttag intbool NUM { $$=pl_newintpredicate($1, $2, $3); } +| datetag datebool dateval { $$=pl_newdatepredicate($1, $2, $3); } +; + +datetag: DATEADDED { $$ = $1; } ; inttag: YEAR @@ -121,6 +137,21 @@ intbool: EQUALS { $$ = $1; } | NOT intbool { $$ = $2 | 0x80000000; } ; +datebool: BEFORE { $$ = $1; } +| AFTER { $$ = $1; } +| NOT datebool { $$=$2 | 0x80000000; } +; + +interval: INTERVAL { $$ = $1; } +| NUM INTERVAL { $$ = $1 * $2; } +; + +dateval: DATE { $$ = $1; } +| interval BEFORE dateval { $$ = $3 - $1; } +| interval AFTER dateval { $$ = $3 + $1; } +| interval AGO { $$ = time(NULL) - $1; } +; + strtag: ARTIST | ALBUM | GENRE @@ -151,6 +182,20 @@ PL_NODE *pl_newintpredicate(int tag, int op, int value) { return pnew; } +PL_NODE *pl_newdatepredicate(int tag, int op, int value) { + PL_NODE *pnew; + + pnew=(PL_NODE*)malloc(sizeof(PL_NODE)); + if(!pnew) + return NULL; + + pnew->op=op; + pnew->type=T_DATE; + pnew->arg1.ival=tag; + pnew->arg2.ival=value; + return pnew; +} + PL_NODE *pl_newcharpredicate(int tag, int op, char *value) { PL_NODE *pnew; diff --git a/src/playlist.c b/src/playlist.c index c8247fd2..f487c0b2 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -25,6 +25,7 @@ #include #include +#include #include "db-memory.h" #include "err.h" @@ -71,6 +72,8 @@ void pl_dump_node(PL_NODE *pnode, int indent) { int index; int not=0; unsigned int boolarg; + char datebuffer[40]; + struct tm *ptm; for(index=0;index "); break; @@ -153,6 +159,12 @@ void pl_dump_node(PL_NODE *pnode, int indent) { case GREATEREQUAL: printf(">= "); break; + case BEFORE: + printf("BEFORE "); + break; + case AFTER: + printf("AFTER "); + break; default: printf(" "); break; @@ -165,6 +177,11 @@ void pl_dump_node(PL_NODE *pnode, int indent) { case T_INT: printf("%d\n",pnode->arg2.ival); break; + case T_DATE: + ptm=localtime((time_t*)&pnode->arg2.ival); + strftime(datebuffer,sizeof(datebuffer),"%Y-%m-%d",ptm); + printf("%s\n",datebuffer); + break; default: printf("\n"); break; @@ -299,6 +316,12 @@ int pl_eval_node(MP3FILE *pmp3, PL_NODE *pnode) { case BITRATE: ival=pmp3->bitrate / 1024; // bitrate in Kbps break; + case DATEADDED: + ival=pmp3->time_added; + break; + default: + DPRINTF(ERR_FATAL,"Unknown token in playlist. This can't happen!\n\n"); + break; } boolarg=(pnode->op) & 0x7FFFFFFF; @@ -323,6 +346,19 @@ int pl_eval_node(MP3FILE *pmp3, PL_NODE *pnode) { } } + if(pnode->type==T_DATE) { + DPRINTF(ERR_DEBUG,"Comparing (datewise) %d to %d\n",ival,pnode->arg2.ival); + switch(boolarg) { + case BEFORE: + r_arg=(ival < pnode->arg2.ival); + break; + case AFTER: + r_arg=(ival > pnode->arg2.ival); + break; + } + retval=r_arg; + } + if(pnode->type==T_INT) { DPRINTF(ERR_DEBUG,"Comparing %d to %d\n",ival,pnode->arg2.ival); @@ -346,7 +382,6 @@ int pl_eval_node(MP3FILE *pmp3, PL_NODE *pnode) { retval = not? !r_arg : r_arg; } - /* can't get here */ DPRINTF(ERR_DEBUG,"Returning %d\n",retval); return retval; } diff --git a/src/playlist.h b/src/playlist.h index d69d18b5..a13692ae 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -26,6 +26,7 @@ #define T_INT 0 #define T_STR 1 +#define T_DATE 2 typedef struct tag_pl_node { int op;