Add support for smart playlist manipulation by date added to database. Updated mt-daapd.playlist to match

This commit is contained in:
Ron Pedde 2004-09-12 23:20:29 +00:00
parent 5463b63436
commit e43caee157
10 changed files with 215 additions and 18 deletions

View File

@ -1,3 +1,7 @@
2204-09-08 Ron Pedde <ron@pedde.com>
* 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 <ron@pedde.com>
* everywhere: fixes for more conformant protocol handling,
support for index tag, meta tag, bug fixes for configuration file

View File

@ -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

View File

@ -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
}

View File

@ -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;
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);

View File

@ -23,12 +23,15 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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;
}

View File

@ -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) {

View File

@ -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 {

View File

@ -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 <cval> ID
%token <ival> NUM
%token <ival> DATE
%token <ival> YEAR
%token <ival> BPM
%token <ival> BITRATE
%token <ival> DATEADDED
%token <ival> BEFORE
%token <ival> AFTER
%token <ival> AGO
%token <ival> INTERVAL
%type <plval> expression
%type <plval> predicate
%type <ival> strtag
%type <ival> inttag
%type <ival> datetag
%type <ival> dateval
%type <ival> interval
%type <ival> strbool
%type <ival> intbool
%type <ival> datebool
%type <ival> 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;

View File

@ -25,6 +25,7 @@
#include <stdio.h>
#include <string.h>
#include <time.h>
#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<indent;index++) {
printf(" ");
@ -122,6 +125,9 @@ void pl_dump_node(PL_NODE *pnode, int indent) {
case BITRATE:
printf("BITRATE ");
break;
case DATEADDED:
printf("DATE ");
break;
default:
printf ("<unknown tag> ");
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("<unknown boolop> ");
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("<unknown type>\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;
}

View File

@ -26,6 +26,7 @@
#define T_INT 0
#define T_STR 1
#define T_DATE 2
typedef struct tag_pl_node {
int op;