mirror of
https://github.com/owntone/owntone-server.git
synced 2025-03-03 07:10:08 -05:00
Add support for smart playlist manipulation by date added to database. Updated mt-daapd.playlist to match
This commit is contained in:
parent
5463b63436
commit
e43caee157
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
50
src/lexer.l
50
src/lexer.l
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
45
src/parser.y
45
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 <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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
#define T_INT 0
|
||||
#define T_STR 1
|
||||
#define T_DATE 2
|
||||
|
||||
typedef struct tag_pl_node {
|
||||
int op;
|
||||
|
Loading…
x
Reference in New Issue
Block a user