Merge pull request #131 from chme/smartplantlr3
Add support for smart playlists
This commit is contained in:
commit
f37833051d
|
@ -269,7 +269,8 @@ Support for iTunes Music Library XML format is available as a compile-time
|
||||||
option. By default, metadata from our parsers is preferred over what's in
|
option. By default, metadata from our parsers is preferred over what's in
|
||||||
the iTunes DB; use itunes_overrides = true if you prefer iTunes' metadata.
|
the iTunes DB; use itunes_overrides = true if you prefer iTunes' metadata.
|
||||||
|
|
||||||
Smart playlists are not supported at the moment.
|
forked-daapd has support for smart playlists. How to create a smart playlist is
|
||||||
|
documented in [README_SMARTPL.md](README_SMARTPL.md).
|
||||||
|
|
||||||
|
|
||||||
## Artwork
|
## Artwork
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
# forked-daapd smart playlists
|
||||||
|
|
||||||
|
|
||||||
|
To add a smart playlist to forked-daapd, create a new text file with a filename ending with .smartpl;
|
||||||
|
the filename doesn't matter, only the .smartpl ending does. The file must be placed somewhere in your
|
||||||
|
library folder.
|
||||||
|
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
The contents of a smart playlist must follow the syntax:
|
||||||
|
|
||||||
|
```
|
||||||
|
"Playlist Name" { expression }
|
||||||
|
```
|
||||||
|
|
||||||
|
There is exactly one smart playlist allowed for a .smartpl file.
|
||||||
|
|
||||||
|
|
||||||
|
An expression consists of:
|
||||||
|
|
||||||
|
```
|
||||||
|
field-name operator operand
|
||||||
|
```
|
||||||
|
|
||||||
|
Where valid field-names (with there types) are:
|
||||||
|
* artist (string)
|
||||||
|
* album_artist (string)
|
||||||
|
* album (string)
|
||||||
|
* title (string)
|
||||||
|
* genre (string)
|
||||||
|
* composer (string)
|
||||||
|
* path (string)
|
||||||
|
* type (string)
|
||||||
|
* data_kind (enumeration)
|
||||||
|
* media_kind (enumeration)
|
||||||
|
* play_count (integer)
|
||||||
|
* rating (integer)
|
||||||
|
* year (integer)
|
||||||
|
* compilation (integer)
|
||||||
|
* time_added (date)
|
||||||
|
* time_played (date)
|
||||||
|
|
||||||
|
Valid operators include:
|
||||||
|
* is, includes (string)
|
||||||
|
* >, <, <=, >=, = (int)
|
||||||
|
* after, before (date)
|
||||||
|
* is (enumeration)
|
||||||
|
|
||||||
|
The "is" operator must exactly match the field value, while the "includes" operator matches a substring.
|
||||||
|
Both matches are case-insensitive.
|
||||||
|
|
||||||
|
Valid operands include:
|
||||||
|
* "string value" (string)
|
||||||
|
* integer (int)
|
||||||
|
|
||||||
|
Valid operands for the enumeration "data_kind" are:
|
||||||
|
* file
|
||||||
|
* url
|
||||||
|
* spotify
|
||||||
|
* pipe
|
||||||
|
|
||||||
|
Valid operands for the enumeration "media_kind" are:
|
||||||
|
* music
|
||||||
|
* movie
|
||||||
|
* podcast
|
||||||
|
* audiobook
|
||||||
|
* tvshow
|
||||||
|
|
||||||
|
|
||||||
|
Multiple expressions can be anded or ored together, using the keywords OR and AND. The unary not operator is also supported using the keyword NOT.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
"techno" {
|
||||||
|
genre includes "techno"
|
||||||
|
and artist includes "zombie"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This would match songs by "Rob Zombie" or "White Zombie", as well as those with a genre of "Techno-Industrial" or
|
||||||
|
"Trance/Techno", for example.
|
||||||
|
|
||||||
|
```
|
||||||
|
"techno 2015" {
|
||||||
|
genre includes "techno"
|
||||||
|
and artist includes "zombie"
|
||||||
|
and not genre includes "industrial"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This would exclude e. g. songs with the genre "Techno-Industrial".
|
||||||
|
|
||||||
|
```
|
||||||
|
"Local music" {
|
||||||
|
data_kind is file
|
||||||
|
and media_kind is music
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This would match all songs added as files to the library that are not placed under the folders for podcasts, audiobooks.
|
||||||
|
|
||||||
|
```
|
||||||
|
"Unplayed podcasts and audiobooks" {
|
||||||
|
play_count = 0
|
||||||
|
and (media_kind is podcast or media_kind is audiobook)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This would match any podcast and audiobook file that was never played with forked-daapd.
|
||||||
|
|
||||||
|
|
||||||
|
## Date oprand syntax
|
||||||
|
|
||||||
|
One example of a valid date is a date in yyyy-mm-dd format:
|
||||||
|
|
||||||
|
```
|
||||||
|
"Files added after January 1, 2004" {
|
||||||
|
time_added 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. Intervals can be defined as "days", "weeks", "months", "years".
|
||||||
|
As an example, a valid date might be:
|
||||||
|
|
||||||
|
```3 weeks before today``` or ```3 weeks ago```
|
||||||
|
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
"Recently Added" {
|
||||||
|
time_added after 2 weeks ago
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This matches all songs added in the last 2 weeks.
|
||||||
|
|
||||||
|
```
|
||||||
|
"Recently played audiobooks" {
|
||||||
|
time_played after last week
|
||||||
|
and media_kind is audiobook
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This matches all audiobooks played in the last week.
|
||||||
|
|
||||||
|
|
||||||
|
## Differences to mt-daapd smart playlists
|
||||||
|
|
||||||
|
The syntax is really close to the mt-daapd smart playlist syntax (see
|
||||||
|
http://sourceforge.net/p/mt-daapd/code/HEAD/tree/tags/release-0.2.4.2/contrib/mt-daapd.playlist).
|
||||||
|
|
||||||
|
Even this documentation is based on the file linked above.
|
||||||
|
|
||||||
|
Some differences are:
|
||||||
|
* only one smart playlist per file
|
||||||
|
* the not operator must be placed before an expression and not before the operator
|
||||||
|
* "||", "&&", "!" are not supported (use "or", "and", "not")
|
||||||
|
* comments are not supported
|
||||||
|
|
|
@ -5,6 +5,7 @@ forked-daapd
|
||||||
*Parser.[ch]
|
*Parser.[ch]
|
||||||
DAAP2SQL.[ch]
|
DAAP2SQL.[ch]
|
||||||
RSP2SQL.[ch]
|
RSP2SQL.[ch]
|
||||||
|
SMARTPL2SQL.[ch]
|
||||||
|
|
||||||
*.u
|
*.u
|
||||||
|
|
||||||
|
|
|
@ -62,13 +62,16 @@ GPERF_PRODUCTS = \
|
||||||
|
|
||||||
ANTLR_GRAMMARS = \
|
ANTLR_GRAMMARS = \
|
||||||
RSP.g RSP2SQL.g \
|
RSP.g RSP2SQL.g \
|
||||||
DAAP.g DAAP2SQL.g
|
DAAP.g DAAP2SQL.g \
|
||||||
|
SMARTPL.g SMARTPL2SQL.g
|
||||||
|
|
||||||
ANTLR_SOURCES = \
|
ANTLR_SOURCES = \
|
||||||
RSPLexer.c RSPLexer.h RSPParser.c RSPParser.h \
|
RSPLexer.c RSPLexer.h RSPParser.c RSPParser.h \
|
||||||
RSP2SQL.c RSP2SQL.h \
|
RSP2SQL.c RSP2SQL.h \
|
||||||
DAAPLexer.c DAAPLexer.h DAAPParser.c DAAPParser.h \
|
DAAPLexer.c DAAPLexer.h DAAPParser.c DAAPParser.h \
|
||||||
DAAP2SQL.c DAAP2SQL.h
|
DAAP2SQL.c DAAP2SQL.h \
|
||||||
|
SMARTPLLexer.c SMARTPLLexer.h SMARTPLParser.c SMARTPLParser.h \
|
||||||
|
SMARTPL2SQL.c SMARTPL2SQL.h
|
||||||
|
|
||||||
ANTLR_PRODUCTS =
|
ANTLR_PRODUCTS =
|
||||||
|
|
||||||
|
@ -95,7 +98,8 @@ forked_daapd_SOURCES = main.c \
|
||||||
conffile.c conffile.h \
|
conffile.c conffile.h \
|
||||||
cache.c cache.h \
|
cache.c cache.h \
|
||||||
filescanner.c filescanner.h \
|
filescanner.c filescanner.h \
|
||||||
filescanner_ffmpeg.c filescanner_playlist.c $(ITUNES_SRC) \
|
filescanner_ffmpeg.c filescanner_playlist.c \
|
||||||
|
filescanner_smartpl.c $(ITUNES_SRC) \
|
||||||
mdns_avahi.c mdns.h \
|
mdns_avahi.c mdns.h \
|
||||||
remote_pairing.c remote_pairing.h \
|
remote_pairing.c remote_pairing.h \
|
||||||
$(EVHTTP_SRC) \
|
$(EVHTTP_SRC) \
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Christian Meffert <christian.meffert@googlemail.com>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
grammar SMARTPL;
|
||||||
|
|
||||||
|
options {
|
||||||
|
output = AST;
|
||||||
|
ASTLabelType = pANTLR3_BASE_TREE;
|
||||||
|
language = C;
|
||||||
|
}
|
||||||
|
|
||||||
|
playlist : STR '{' expression '}' EOF
|
||||||
|
;
|
||||||
|
|
||||||
|
expression : aexpr (OR^ aexpr)*
|
||||||
|
;
|
||||||
|
|
||||||
|
aexpr : nexpr (AND^ nexpr)*
|
||||||
|
;
|
||||||
|
|
||||||
|
nexpr : NOT^ crit
|
||||||
|
| crit
|
||||||
|
;
|
||||||
|
|
||||||
|
crit : LPAR expression RPAR -> expression
|
||||||
|
| STRTAG (INCLUDES|IS) STR
|
||||||
|
| INTTAG INTBOOL INT
|
||||||
|
| DATETAG (AFTER|BEFORE) dateval
|
||||||
|
| ENUMTAG IS ENUMVAL
|
||||||
|
;
|
||||||
|
|
||||||
|
dateval : DATE
|
||||||
|
| interval BEFORE DATE
|
||||||
|
| interval AFTER DATE
|
||||||
|
| interval AGO
|
||||||
|
;
|
||||||
|
|
||||||
|
interval : INT DATINTERVAL
|
||||||
|
;
|
||||||
|
|
||||||
|
STRTAG : 'artist'
|
||||||
|
| 'album_artist'
|
||||||
|
| 'album'
|
||||||
|
| 'title'
|
||||||
|
| 'genre'
|
||||||
|
| 'composer'
|
||||||
|
| 'path'
|
||||||
|
| 'type'
|
||||||
|
;
|
||||||
|
|
||||||
|
INTTAG : 'play_count'
|
||||||
|
| 'rating'
|
||||||
|
| 'year'
|
||||||
|
| 'compilation'
|
||||||
|
;
|
||||||
|
|
||||||
|
DATETAG : 'time_added'
|
||||||
|
| 'time_played'
|
||||||
|
;
|
||||||
|
|
||||||
|
ENUMTAG : 'data_kind'
|
||||||
|
| 'media_kind'
|
||||||
|
;
|
||||||
|
|
||||||
|
INCLUDES : 'includes'
|
||||||
|
;
|
||||||
|
|
||||||
|
IS : 'is'
|
||||||
|
;
|
||||||
|
|
||||||
|
INTBOOL : (GREATER|GREATEREQUAL|LESS|LESSEQUAL|EQUAL)
|
||||||
|
;
|
||||||
|
|
||||||
|
fragment
|
||||||
|
GREATER : '>'
|
||||||
|
;
|
||||||
|
|
||||||
|
fragment
|
||||||
|
GREATEREQUAL: '>='
|
||||||
|
;
|
||||||
|
|
||||||
|
fragment
|
||||||
|
LESS : '<'
|
||||||
|
;
|
||||||
|
|
||||||
|
fragment
|
||||||
|
LESSEQUAL : '<='
|
||||||
|
;
|
||||||
|
|
||||||
|
fragment
|
||||||
|
EQUAL : '='
|
||||||
|
;
|
||||||
|
|
||||||
|
AFTER : 'after'
|
||||||
|
;
|
||||||
|
|
||||||
|
BEFORE : 'before'
|
||||||
|
;
|
||||||
|
|
||||||
|
AGO : 'ago'
|
||||||
|
;
|
||||||
|
|
||||||
|
AND : 'AND'
|
||||||
|
| 'and'
|
||||||
|
;
|
||||||
|
|
||||||
|
OR : 'OR'
|
||||||
|
| 'or'
|
||||||
|
;
|
||||||
|
|
||||||
|
NOT : 'NOT'
|
||||||
|
| 'not'
|
||||||
|
;
|
||||||
|
|
||||||
|
LPAR : '('
|
||||||
|
;
|
||||||
|
|
||||||
|
RPAR : ')'
|
||||||
|
;
|
||||||
|
|
||||||
|
DATE : ('0'..'9')('0'..'9')('0'..'9')('0'..'9')'-'('0'..'1')('0'..'9')'-'('0'..'3')('0'..'9')
|
||||||
|
| 'today'
|
||||||
|
| 'yesterday'
|
||||||
|
| 'last week'
|
||||||
|
| 'last month'
|
||||||
|
| 'last year'
|
||||||
|
;
|
||||||
|
|
||||||
|
DATINTERVAL : 'days'
|
||||||
|
| 'weeks'
|
||||||
|
| 'months'
|
||||||
|
| 'years'
|
||||||
|
;
|
||||||
|
|
||||||
|
ENUMVAL : 'music'
|
||||||
|
| 'movie'
|
||||||
|
| 'podcast'
|
||||||
|
| 'audiobook'
|
||||||
|
| 'tvshow'
|
||||||
|
| 'file'
|
||||||
|
| 'url'
|
||||||
|
| 'spotify'
|
||||||
|
| 'pipe'
|
||||||
|
;
|
||||||
|
|
||||||
|
STR : '"' ~('"')+ '"'
|
||||||
|
;
|
||||||
|
|
||||||
|
INT : ('0'..'9')+
|
||||||
|
;
|
||||||
|
|
||||||
|
WHITESPACE : ('\t'|' '|'\r'|'\n'|'\u000C') { $channel = HIDDEN; }
|
||||||
|
;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,352 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Christian Meffert <christian.meffert@googlemail.com>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
tree grammar SMARTPL2SQL;
|
||||||
|
|
||||||
|
options {
|
||||||
|
tokenVocab = SMARTPL;
|
||||||
|
ASTLabelType = pANTLR3_BASE_TREE;
|
||||||
|
language = C;
|
||||||
|
}
|
||||||
|
|
||||||
|
@header {
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
#include "db.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
@members {
|
||||||
|
}
|
||||||
|
|
||||||
|
playlist returns [ pANTLR3_STRING title, pANTLR3_STRING query ]
|
||||||
|
@init { $title = NULL; $query = NULL; }
|
||||||
|
: STR '{' e = expression '}'
|
||||||
|
{
|
||||||
|
pANTLR3_UINT8 val;
|
||||||
|
val = $STR.text->toUTF8($STR.text)->chars;
|
||||||
|
val++;
|
||||||
|
val[strlen((const char *)val) - 1] = '\0';
|
||||||
|
|
||||||
|
$title = $STR.text->factory->newRaw($STR.text->factory);
|
||||||
|
$title->append8($title, (const char *)val);
|
||||||
|
|
||||||
|
$query = $e.result->factory->newRaw($e.result->factory);
|
||||||
|
$query->append8($query, "(");
|
||||||
|
$query->appendS($query, $e.result);
|
||||||
|
$query->append8($query, ")");
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
expression returns [ pANTLR3_STRING result ]
|
||||||
|
@init { $result = NULL; }
|
||||||
|
: ^(NOT a = expression)
|
||||||
|
{
|
||||||
|
$result = $a.result->factory->newRaw($a.result->factory);
|
||||||
|
$result->append8($result, "NOT(");
|
||||||
|
$result->appendS($result, $a.result);
|
||||||
|
$result->append8($result, ")");
|
||||||
|
}
|
||||||
|
| ^(AND a = expression b = expression)
|
||||||
|
{
|
||||||
|
$result = $a.result->factory->newRaw($a.result->factory);
|
||||||
|
$result->append8($result, "(");
|
||||||
|
$result->appendS($result, $a.result);
|
||||||
|
$result->append8($result, " AND ");
|
||||||
|
$result->appendS($result, $b.result);
|
||||||
|
$result->append8($result, ")");
|
||||||
|
}
|
||||||
|
| ^(OR a = expression b = expression)
|
||||||
|
{
|
||||||
|
$result = $a.result->factory->newRaw($a.result->factory);
|
||||||
|
$result->append8($result, "(");
|
||||||
|
$result->appendS($result, $a.result);
|
||||||
|
$result->append8($result, " OR ");
|
||||||
|
$result->appendS($result, $b.result);
|
||||||
|
$result->append8($result, ")");
|
||||||
|
}
|
||||||
|
| STRTAG INCLUDES STR
|
||||||
|
{
|
||||||
|
pANTLR3_UINT8 val;
|
||||||
|
val = $STR.text->toUTF8($STR.text)->chars;
|
||||||
|
val++;
|
||||||
|
val[strlen((const char *)val) - 1] = '\0';
|
||||||
|
|
||||||
|
$result = $STR.text->factory->newRaw($STR.text->factory);
|
||||||
|
$result->append8($result, "f.");
|
||||||
|
$result->appendS($result, $STRTAG.text->toUTF8($STRTAG.text));
|
||||||
|
$result->append8($result, " LIKE '\%");
|
||||||
|
$result->append8($result, sqlite3_mprintf("\%q", (const char *)val));
|
||||||
|
$result->append8($result, "\%'");
|
||||||
|
}
|
||||||
|
| STRTAG IS STR
|
||||||
|
{
|
||||||
|
pANTLR3_UINT8 val;
|
||||||
|
val = $STR.text->toUTF8($STR.text)->chars;
|
||||||
|
val++;
|
||||||
|
val[strlen((const char *)val) - 1] = '\0';
|
||||||
|
|
||||||
|
$result = $STR.text->factory->newRaw($STR.text->factory);
|
||||||
|
$result->append8($result, "f.");
|
||||||
|
$result->appendS($result, $STRTAG.text->toUTF8($STRTAG.text));
|
||||||
|
$result->append8($result, " LIKE '");
|
||||||
|
$result->append8($result, sqlite3_mprintf("\%q", (const char *)val));
|
||||||
|
$result->append8($result, "'");
|
||||||
|
}
|
||||||
|
| INTTAG INTBOOL INT
|
||||||
|
{
|
||||||
|
$result = $INTTAG.text->factory->newRaw($INTTAG.text->factory);
|
||||||
|
$result->append8($result, "f.");
|
||||||
|
$result->appendS($result, $INTTAG.text->toUTF8($INTTAG.text));
|
||||||
|
$result->append8($result, " ");
|
||||||
|
$result->appendS($result, $INTBOOL.text->toUTF8($INTBOOL.text));
|
||||||
|
$result->append8($result, " ");
|
||||||
|
$result->appendS($result, $INT.text->toUTF8($INT.text));
|
||||||
|
}
|
||||||
|
| DATETAG AFTER dateval
|
||||||
|
{
|
||||||
|
char str[15];
|
||||||
|
sprintf(str, "\%d", $dateval.result);
|
||||||
|
|
||||||
|
$result = $DATETAG.text->factory->newRaw($DATETAG.text->factory);
|
||||||
|
$result->append8($result, "f.");
|
||||||
|
$result->appendS($result, $DATETAG.text->toUTF8($DATETAG.text));
|
||||||
|
$result->append8($result, " > ");
|
||||||
|
$result->append8($result, str);
|
||||||
|
}
|
||||||
|
| DATETAG BEFORE dateval
|
||||||
|
{
|
||||||
|
char str[15];
|
||||||
|
sprintf(str, "\%d", $dateval.result);
|
||||||
|
|
||||||
|
$result = $DATETAG.text->factory->newRaw($DATETAG.text->factory);
|
||||||
|
$result->append8($result, "f.");
|
||||||
|
$result->appendS($result, $DATETAG.text->toUTF8($DATETAG.text));
|
||||||
|
$result->append8($result, " > ");
|
||||||
|
$result->append8($result, str);
|
||||||
|
}
|
||||||
|
| ENUMTAG IS ENUMVAL
|
||||||
|
{
|
||||||
|
pANTLR3_UINT8 tag;
|
||||||
|
pANTLR3_UINT8 val;
|
||||||
|
char str[20];
|
||||||
|
|
||||||
|
sprintf(str, "1=1");
|
||||||
|
|
||||||
|
tag = $ENUMTAG.text->chars;
|
||||||
|
val = $ENUMVAL.text->chars;
|
||||||
|
if (strcmp((char *)tag, "media_kind") == 0)
|
||||||
|
{
|
||||||
|
if (strcmp((char *)val, "music") == 0)
|
||||||
|
{
|
||||||
|
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_MUSIC);
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)val, "movie") == 0)
|
||||||
|
{
|
||||||
|
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_MOVIE);
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)val, "podcast") == 0)
|
||||||
|
{
|
||||||
|
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_PODCAST);
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)val, "audiobook") == 0)
|
||||||
|
{
|
||||||
|
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_AUDIOBOOK);
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)val, "tvshow") == 0)
|
||||||
|
{
|
||||||
|
sprintf(str, "f.media_kind = \%d", MEDIA_KIND_TVSHOW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)tag, "data_kind") == 0)
|
||||||
|
{
|
||||||
|
if (strcmp((char *)val, "file") == 0)
|
||||||
|
{
|
||||||
|
sprintf(str, "f.data_kind = \%d", DATA_KIND_FILE);
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)val, "url") == 0)
|
||||||
|
{
|
||||||
|
sprintf(str, "f.data_kind = \%d", DATA_KIND_URL);
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)val, "spotify") == 0)
|
||||||
|
{
|
||||||
|
sprintf(str, "f.data_kind = \%d", DATA_KIND_SPOTIFY);
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)val, "pipe") == 0)
|
||||||
|
{
|
||||||
|
sprintf(str, "f.data_kind = \%d", DATA_KIND_PIPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $ENUMTAG.text->factory->newRaw($ENUMTAG.text->factory);
|
||||||
|
$result->append8($result, str);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
dateval returns [ int result ]
|
||||||
|
@init { $result = 0; }
|
||||||
|
: DATE
|
||||||
|
{
|
||||||
|
pANTLR3_UINT8 datval;
|
||||||
|
|
||||||
|
datval = $DATE.text->chars;
|
||||||
|
|
||||||
|
if (strcmp((char *)datval, "today") == 0)
|
||||||
|
{
|
||||||
|
$result = time(NULL);
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)datval, "yesterday") == 0)
|
||||||
|
{
|
||||||
|
$result = time(NULL) - 24 * 3600;
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)datval, "last week") == 0)
|
||||||
|
{
|
||||||
|
$result = time(NULL) - 24 * 3600 * 7;
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)datval, "last month") == 0)
|
||||||
|
{
|
||||||
|
$result = time(NULL) - 24 * 3600 * 30;
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)datval, "last year") == 0)
|
||||||
|
{
|
||||||
|
$result = time(NULL) - 24 * 3600 * 365;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
struct tm tm;
|
||||||
|
char year[5];
|
||||||
|
char month[3];
|
||||||
|
char day[3];
|
||||||
|
|
||||||
|
memset((void*)&tm,0,sizeof(tm));
|
||||||
|
memset(year, 0, sizeof(year));
|
||||||
|
memset(month, 0, sizeof(month));
|
||||||
|
memset(day, 0, sizeof(day));
|
||||||
|
|
||||||
|
strncpy(year, (const char *)datval, 4);
|
||||||
|
strncpy(month, (const char *)datval + 5, 2);
|
||||||
|
strncpy(day, (const char *)datval + 8, 2);
|
||||||
|
|
||||||
|
tm.tm_year = atoi(year) - 1900;
|
||||||
|
tm.tm_mon = atoi(month) - 1;
|
||||||
|
tm.tm_mday = atoi(day);
|
||||||
|
|
||||||
|
$result = mktime(&tm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| interval BEFORE DATE
|
||||||
|
{
|
||||||
|
pANTLR3_UINT8 datval;
|
||||||
|
|
||||||
|
datval = $DATE.text->chars;
|
||||||
|
|
||||||
|
if (strcmp((char *)datval, "yesterday") == 0)
|
||||||
|
{
|
||||||
|
$result = time(NULL) - 24 * 3600;
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)datval, "last week") == 0)
|
||||||
|
{
|
||||||
|
$result = time(NULL) - 24 * 3600 * 7;
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)datval, "last month") == 0)
|
||||||
|
{
|
||||||
|
$result = time(NULL) - 24 * 3600 * 30;
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)datval, "last year") == 0)
|
||||||
|
{
|
||||||
|
$result = time(NULL) - 24 * 3600 * 365;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$result = time(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $result - $interval.result;
|
||||||
|
}
|
||||||
|
| interval AFTER DATE
|
||||||
|
{
|
||||||
|
pANTLR3_UINT8 datval;
|
||||||
|
|
||||||
|
datval = $DATE.text->chars;
|
||||||
|
|
||||||
|
if (strcmp((char *)datval, "yesterday") == 0)
|
||||||
|
{
|
||||||
|
$result = time(NULL) - 24 * 3600;
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)datval, "last week") == 0)
|
||||||
|
{
|
||||||
|
$result = time(NULL) - 24 * 3600 * 7;
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)datval, "last month") == 0)
|
||||||
|
{
|
||||||
|
$result = time(NULL) - 24 * 3600 * 30;
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)datval, "last year") == 0)
|
||||||
|
{
|
||||||
|
$result = time(NULL) - 24 * 3600 * 365;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$result = time(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $result + $interval.result;
|
||||||
|
}
|
||||||
|
| interval AGO
|
||||||
|
{
|
||||||
|
$result = time(NULL) - $interval.result;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
interval returns [ int result ]
|
||||||
|
@init { $result = 0; }
|
||||||
|
: INT DATINTERVAL
|
||||||
|
{
|
||||||
|
pANTLR3_UINT8 interval;
|
||||||
|
|
||||||
|
$result = atoi((const char *)$INT.text->chars);
|
||||||
|
interval = $DATINTERVAL.text->chars;
|
||||||
|
|
||||||
|
if (strcmp((char *)interval, "days") == 0)
|
||||||
|
{
|
||||||
|
$result = $result * 24 * 3600;
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)interval, "weeks") == 0)
|
||||||
|
{
|
||||||
|
$result = $result * 24 * 3600 * 7;
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)interval, "months") == 0)
|
||||||
|
{
|
||||||
|
$result = $result * 24 * 3600 * 30;
|
||||||
|
}
|
||||||
|
else if (strcmp((char *)interval, "weeks") == 0)
|
||||||
|
{
|
||||||
|
$result = $result * 24 * 3600 * 365;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$result = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
|
@ -1106,7 +1106,7 @@ artwork_get_item_mfi(struct evbuffer *evbuf, struct media_file_info *mfi, int ma
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mfi->data_kind == 0)
|
if (mfi->data_kind == DATA_KIND_FILE)
|
||||||
{
|
{
|
||||||
format = artwork_get_item_path(evbuf, mfi->path, mfi->artwork, max_w, max_h, path);
|
format = artwork_get_item_path(evbuf, mfi->path, mfi->artwork, max_w, max_h, path);
|
||||||
|
|
||||||
|
|
104
src/db.c
104
src/db.c
|
@ -281,7 +281,7 @@ static const char *sort_clause[] =
|
||||||
"ORDER BY f.title_sort ASC",
|
"ORDER BY f.title_sort ASC",
|
||||||
"ORDER BY f.album_sort ASC, f.disc ASC, f.track ASC",
|
"ORDER BY f.album_sort ASC, f.disc ASC, f.track ASC",
|
||||||
"ORDER BY f.album_artist_sort ASC",
|
"ORDER BY f.album_artist_sort ASC",
|
||||||
"ORDER BY f.type DESC, f.parent_id ASC, f.special_id ASC, f.title ASC",
|
"ORDER BY f.type ASC, f.parent_id ASC, f.special_id ASC, f.title ASC",
|
||||||
"ORDER BY f.year ASC",
|
"ORDER BY f.year ASC",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -663,7 +663,7 @@ db_set_cfg_names(void)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
query = sqlite3_mprintf(Q_TMPL, title, PL_SMART, special_id[i]);
|
query = sqlite3_mprintf(Q_TMPL, title, PL_SPECIAL, special_id[i]);
|
||||||
if (!query)
|
if (!query)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
||||||
|
@ -718,7 +718,7 @@ db_purge_cruft(time_t ref)
|
||||||
|
|
||||||
for (i = 0; i < (sizeof(queries_tmpl) / sizeof(queries_tmpl[0])); i++)
|
for (i = 0; i < (sizeof(queries_tmpl) / sizeof(queries_tmpl[0])); i++)
|
||||||
{
|
{
|
||||||
queries[i] = sqlite3_mprintf(queries_tmpl[i], PL_SMART, (int64_t)ref);
|
queries[i] = sqlite3_mprintf(queries_tmpl[i], PL_SPECIAL, (int64_t)ref);
|
||||||
if (!queries[i])
|
if (!queries[i])
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
||||||
|
@ -780,7 +780,7 @@ db_purge_all(void)
|
||||||
DPRINTF(E_DBG, L_DB, "Purged %d rows\n", sqlite3_changes(hdl));
|
DPRINTF(E_DBG, L_DB, "Purged %d rows\n", sqlite3_changes(hdl));
|
||||||
}
|
}
|
||||||
|
|
||||||
query = sqlite3_mprintf(Q_TMPL, PL_SMART);
|
query = sqlite3_mprintf(Q_TMPL, PL_SPECIAL);
|
||||||
if (!query)
|
if (!query)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
||||||
|
@ -1141,6 +1141,7 @@ db_build_query_plitems(struct query_params *qp, char **q)
|
||||||
|
|
||||||
switch (pli->type)
|
switch (pli->type)
|
||||||
{
|
{
|
||||||
|
case PL_SPECIAL:
|
||||||
case PL_SMART:
|
case PL_SMART:
|
||||||
ret = db_build_query_plitems_smart(qp, pli->query, q);
|
ret = db_build_query_plitems_smart(qp, pli->query, q);
|
||||||
break;
|
break;
|
||||||
|
@ -1722,6 +1723,7 @@ db_query_fetch_pl(struct query_params *qp, struct db_playlist_info *dbpli)
|
||||||
nstreams = db_pl_count_items(id, 1);
|
nstreams = db_pl_count_items(id, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PL_SPECIAL:
|
||||||
case PL_SMART:
|
case PL_SMART:
|
||||||
nitems = db_smartpl_count_items(dbpli->query);
|
nitems = db_smartpl_count_items(dbpli->query);
|
||||||
nstreams = 0;
|
nstreams = 0;
|
||||||
|
@ -3041,6 +3043,7 @@ db_pl_fetch_byquery(char *query)
|
||||||
pli->streams = db_pl_count_items(pli->id, 1);
|
pli->streams = db_pl_count_items(pli->id, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PL_SPECIAL:
|
||||||
case PL_SMART:
|
case PL_SMART:
|
||||||
pli->items = db_smartpl_count_items(pli->query);
|
pli->items = db_smartpl_count_items(pli->query);
|
||||||
break;
|
break;
|
||||||
|
@ -3156,7 +3159,7 @@ db_pl_add(struct playlist_info *pli, int *id)
|
||||||
{
|
{
|
||||||
#define QDUP_TMPL "SELECT COUNT(*) FROM playlists p WHERE p.title = TRIM(%Q) AND p.path = '%q';"
|
#define QDUP_TMPL "SELECT COUNT(*) FROM playlists p WHERE p.title = TRIM(%Q) AND p.path = '%q';"
|
||||||
#define QADD_TMPL "INSERT INTO playlists (title, type, query, db_timestamp, disabled, path, idx, special_id, parent_id, virtual_path)" \
|
#define QADD_TMPL "INSERT INTO playlists (title, type, query, db_timestamp, disabled, path, idx, special_id, parent_id, virtual_path)" \
|
||||||
" VALUES (TRIM(%Q), %d, NULL, %" PRIi64 ", %d, '%q', %d, %d, %d, '%q');"
|
" VALUES (TRIM(%Q), %d, '%q', %" PRIi64 ", %d, '%q', %d, %d, %d, '%q');"
|
||||||
char *query;
|
char *query;
|
||||||
char *errmsg;
|
char *errmsg;
|
||||||
int ret;
|
int ret;
|
||||||
|
@ -3181,7 +3184,7 @@ db_pl_add(struct playlist_info *pli, int *id)
|
||||||
|
|
||||||
/* Add */
|
/* Add */
|
||||||
query = sqlite3_mprintf(QADD_TMPL,
|
query = sqlite3_mprintf(QADD_TMPL,
|
||||||
pli->title, pli->type, (int64_t)time(NULL), pli->disabled, STR(pli->path),
|
pli->title, pli->type, pli->query, (int64_t)time(NULL), pli->disabled, STR(pli->path),
|
||||||
pli->index, pli->special_id, pli->parent_id, pli->virtual_path);
|
pli->index, pli->special_id, pli->parent_id, pli->virtual_path);
|
||||||
|
|
||||||
if (!query)
|
if (!query)
|
||||||
|
@ -3246,14 +3249,14 @@ db_pl_add_item_byid(int plid, int fileid)
|
||||||
int
|
int
|
||||||
db_pl_update(struct playlist_info *pli)
|
db_pl_update(struct playlist_info *pli)
|
||||||
{
|
{
|
||||||
#define Q_TMPL "UPDATE playlists SET title = TRIM(%Q), type = %d, db_timestamp = %" PRIi64 ", disabled = %d, path = '%q', " \
|
#define Q_TMPL "UPDATE playlists SET title = TRIM(%Q), type = %d, query = '%q', db_timestamp = %" PRIi64 ", disabled = %d, " \
|
||||||
" idx = %d, special_id = %d, parent_id = %d, virtual_path = '%q' " \
|
" path = '%q', idx = %d, special_id = %d, parent_id = %d, virtual_path = '%q' " \
|
||||||
" WHERE id = %d;"
|
" WHERE id = %d;"
|
||||||
char *query;
|
char *query;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
query = sqlite3_mprintf(Q_TMPL,
|
query = sqlite3_mprintf(Q_TMPL,
|
||||||
pli->title, pli->type, (int64_t)time(NULL), pli->disabled, STR(pli->path),
|
pli->title, pli->type, pli->query, (int64_t)time(NULL), pli->disabled, STR(pli->path),
|
||||||
pli->index, pli->special_id, pli->parent_id, pli->virtual_path, pli->id);
|
pli->index, pli->special_id, pli->parent_id, pli->virtual_path, pli->id);
|
||||||
|
|
||||||
ret = db_query_run(query, 1, 0);
|
ret = db_query_run(query, 1, 0);
|
||||||
|
@ -4580,15 +4583,15 @@ db_perthread_deinit(void)
|
||||||
" path VARCHAR(4096) NOT NULL" \
|
" path VARCHAR(4096) NOT NULL" \
|
||||||
");"
|
");"
|
||||||
|
|
||||||
#define V_FILELIST \
|
#define V_FILELIST \
|
||||||
"CREATE VIEW IF NOT EXISTS filelist as" \
|
"CREATE VIEW IF NOT EXISTS filelist as" \
|
||||||
" SELECT " \
|
" SELECT " \
|
||||||
" virtual_path, time_modified, 3 as type " \
|
" virtual_path, time_modified, 3 as type " \
|
||||||
" FROM files WHERE disabled = 0" \
|
" FROM files WHERE disabled = 0" \
|
||||||
" UNION " \
|
" UNION " \
|
||||||
" SELECT " \
|
" SELECT " \
|
||||||
" virtual_path, db_timestamp, 1 as type " \
|
" virtual_path, db_timestamp, 1 as type " \
|
||||||
" FROM playlists where disabled = 0 AND type = 0" \
|
" FROM playlists where disabled = 0 AND type IN (2, 3)" \
|
||||||
";"
|
";"
|
||||||
|
|
||||||
#define TRG_GROUPS_INSERT_FILES \
|
#define TRG_GROUPS_INSERT_FILES \
|
||||||
|
@ -4607,27 +4610,27 @@ db_perthread_deinit(void)
|
||||||
|
|
||||||
#define Q_PL1 \
|
#define Q_PL1 \
|
||||||
"INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
|
"INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
|
||||||
" VALUES(1, 'Library', 2, '1 = 1', 0, '', 0, 0);"
|
" VALUES(1, 'Library', 0, '1 = 1', 0, '', 0, 0);"
|
||||||
|
|
||||||
#define Q_PL2 \
|
#define Q_PL2 \
|
||||||
"INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
|
"INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
|
||||||
" VALUES(2, 'Music', 2, 'f.media_kind = 1', 0, '', 0, 6);"
|
" VALUES(2, 'Music', 0, 'f.media_kind = 1', 0, '', 0, 6);"
|
||||||
|
|
||||||
#define Q_PL3 \
|
#define Q_PL3 \
|
||||||
"INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
|
"INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
|
||||||
" VALUES(3, 'Movies', 2, 'f.media_kind = 2', 0, '', 0, 4);"
|
" VALUES(3, 'Movies', 0, 'f.media_kind = 2', 0, '', 0, 4);"
|
||||||
|
|
||||||
#define Q_PL4 \
|
#define Q_PL4 \
|
||||||
"INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
|
"INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
|
||||||
" VALUES(4, 'TV Shows', 2, 'f.media_kind = 64', 0, '', 0, 5);"
|
" VALUES(4, 'TV Shows', 0, 'f.media_kind = 64', 0, '', 0, 5);"
|
||||||
|
|
||||||
#define Q_PL5 \
|
#define Q_PL5 \
|
||||||
"INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
|
"INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
|
||||||
" VALUES(5, 'Podcasts', 2, 'f.media_kind = 4', 0, '', 0, 1);"
|
" VALUES(5, 'Podcasts', 0, 'f.media_kind = 4', 0, '', 0, 1);"
|
||||||
|
|
||||||
#define Q_PL6 \
|
#define Q_PL6 \
|
||||||
"INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
|
"INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
|
||||||
" VALUES(6, 'Audiobooks', 2, 'f.media_kind = 8', 0, '', 0, 7);"
|
" VALUES(6, 'Audiobooks', 0, 'f.media_kind = 8', 0, '', 0, 7);"
|
||||||
|
|
||||||
/* These are the remaining automatically-created iTunes playlists, but
|
/* These are the remaining automatically-created iTunes playlists, but
|
||||||
* their query is unknown
|
* their query is unknown
|
||||||
|
@ -4635,10 +4638,10 @@ db_perthread_deinit(void)
|
||||||
" VALUES(8, 'Purchased', 0, 'media_kind = 1024', 0, '', 0, 8);"
|
" VALUES(8, 'Purchased', 0, 'media_kind = 1024', 0, '', 0, 8);"
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define SCHEMA_VERSION_MAJOR 17
|
#define SCHEMA_VERSION_MAJOR 18
|
||||||
#define SCHEMA_VERSION_MINOR 00
|
#define SCHEMA_VERSION_MINOR 00
|
||||||
#define Q_SCVER_MAJOR \
|
#define Q_SCVER_MAJOR \
|
||||||
"INSERT INTO admin (key, value) VALUES ('schema_version_major', '17');"
|
"INSERT INTO admin (key, value) VALUES ('schema_version_major', '18');"
|
||||||
#define Q_SCVER_MINOR \
|
#define Q_SCVER_MINOR \
|
||||||
"INSERT INTO admin (key, value) VALUES ('schema_version_minor', '00');"
|
"INSERT INTO admin (key, value) VALUES ('schema_version_minor', '00');"
|
||||||
|
|
||||||
|
@ -5810,7 +5813,7 @@ db_upgrade_v16(void)
|
||||||
path = (char *)sqlite3_column_text(stmt, 2);
|
path = (char *)sqlite3_column_text(stmt, 2);
|
||||||
type = sqlite3_column_int(stmt, 3);
|
type = sqlite3_column_int(stmt, 3);
|
||||||
|
|
||||||
if (type == PL_PLAIN) /* Excludes default/Smart playlists and playlist folders */
|
if (type == 0) /* Excludes default/Smart playlists and playlist folders */
|
||||||
{
|
{
|
||||||
if (strncmp(path, "spotify:", strlen("spotify:")) == 0)
|
if (strncmp(path, "spotify:", strlen("spotify:")) == 0)
|
||||||
snprintf(virtual_path, PATH_MAX, "/spotify:/%s", title);
|
snprintf(virtual_path, PATH_MAX, "/spotify:/%s", title);
|
||||||
|
@ -5858,6 +5861,44 @@ static const struct db_init_query db_upgrade_v17_queries[] =
|
||||||
{ U_V17_SCVER_MINOR, "set schema_version_minor to 00" },
|
{ U_V17_SCVER_MINOR, "set schema_version_minor to 00" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Upgrade from schema v17.00 to v18.00 */
|
||||||
|
/* Change playlist type enumeration and recreate filelist view (include smart
|
||||||
|
* playlists in view)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define U_V18_PL_TYPE_CHANGE_PLAIN \
|
||||||
|
"UPDATE playlists SET type = 3 WHERE type = 0;"
|
||||||
|
#define U_V18_PL_TYPE_CHANGE_SPECIAL \
|
||||||
|
"UPDATE playlists SET type = 0 WHERE type = 2;"
|
||||||
|
#define U_V18_DROP_VIEW_FILELIST \
|
||||||
|
"DROP VIEW IF EXISTS filelist;"
|
||||||
|
#define U_V18_CREATE_VIEW_FILELIST \
|
||||||
|
"CREATE VIEW IF NOT EXISTS filelist as" \
|
||||||
|
" SELECT " \
|
||||||
|
" virtual_path, time_modified, 3 as type " \
|
||||||
|
" FROM files WHERE disabled = 0" \
|
||||||
|
" UNION " \
|
||||||
|
" SELECT " \
|
||||||
|
" virtual_path, db_timestamp, 1 as type " \
|
||||||
|
" FROM playlists where disabled = 0 AND type IN (2, 3)" \
|
||||||
|
";"
|
||||||
|
|
||||||
|
#define U_V18_SCVER_MAJOR \
|
||||||
|
"UPDATE admin SET value = '18' WHERE key = 'schema_version_major';"
|
||||||
|
#define U_V18_SCVER_MINOR \
|
||||||
|
"UPDATE admin SET value = '00' WHERE key = 'schema_version_minor';"
|
||||||
|
|
||||||
|
static const struct db_init_query db_upgrade_v18_queries[] =
|
||||||
|
{
|
||||||
|
{ U_V18_PL_TYPE_CHANGE_PLAIN, "changing numbering of plain playlists 0 -> 3" },
|
||||||
|
{ U_V18_PL_TYPE_CHANGE_SPECIAL, "changing numbering of default playlists 2 -> 0" },
|
||||||
|
{ U_V18_DROP_VIEW_FILELIST, "dropping view filelist" },
|
||||||
|
{ U_V18_CREATE_VIEW_FILELIST, "creating view filelist" },
|
||||||
|
|
||||||
|
{ U_V18_SCVER_MAJOR, "set schema_version_major to 18" },
|
||||||
|
{ U_V18_SCVER_MINOR, "set schema_version_minor to 00" },
|
||||||
|
};
|
||||||
|
|
||||||
static int
|
static int
|
||||||
db_upgrade(int db_ver)
|
db_upgrade(int db_ver)
|
||||||
{
|
{
|
||||||
|
@ -5938,6 +5979,15 @@ db_upgrade(int db_ver)
|
||||||
|
|
||||||
case 1600:
|
case 1600:
|
||||||
ret = db_generic_upgrade(db_upgrade_v17_queries, sizeof(db_upgrade_v17_queries) / sizeof(db_upgrade_v17_queries[0]));
|
ret = db_generic_upgrade(db_upgrade_v17_queries, sizeof(db_upgrade_v17_queries) / sizeof(db_upgrade_v17_queries[0]));
|
||||||
|
if (ret < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* FALLTHROUGH */
|
||||||
|
|
||||||
|
case 1700:
|
||||||
|
ret = db_generic_upgrade(db_upgrade_v18_queries, sizeof(db_upgrade_v18_queries) / sizeof(db_upgrade_v18_queries[0]));
|
||||||
|
if (ret < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
20
src/db.h
20
src/db.h
|
@ -85,6 +85,21 @@ struct pairing_info {
|
||||||
char *guid;
|
char *guid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum media_kind {
|
||||||
|
MEDIA_KIND_MUSIC = 0,
|
||||||
|
MEDIA_KIND_MOVIE = 2,
|
||||||
|
MEDIA_KIND_PODCAST = 4,
|
||||||
|
MEDIA_KIND_AUDIOBOOK = 8,
|
||||||
|
MEDIA_KIND_TVSHOW = 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum data_kind {
|
||||||
|
DATA_KIND_FILE = 0,
|
||||||
|
DATA_KIND_URL = 1,
|
||||||
|
DATA_KIND_SPOTIFY = 2,
|
||||||
|
DATA_KIND_PIPE = 3,
|
||||||
|
};
|
||||||
|
|
||||||
struct media_file_info {
|
struct media_file_info {
|
||||||
char *path;
|
char *path;
|
||||||
uint32_t index;
|
uint32_t index;
|
||||||
|
@ -166,11 +181,12 @@ struct media_file_info {
|
||||||
|
|
||||||
#define mfi_offsetof(field) offsetof(struct media_file_info, field)
|
#define mfi_offsetof(field) offsetof(struct media_file_info, field)
|
||||||
|
|
||||||
/* PL_SMART value must be in sync with type value in Q_PL* in db.c */
|
/* PL_SPECIAL value must be in sync with type value in Q_PL* in db.c */
|
||||||
enum pl_type {
|
enum pl_type {
|
||||||
PL_PLAIN = 0,
|
PL_SPECIAL = 0,
|
||||||
PL_FOLDER = 1,
|
PL_FOLDER = 1,
|
||||||
PL_SMART = 2,
|
PL_SMART = 2,
|
||||||
|
PL_PLAIN = 3,
|
||||||
PL_MAX,
|
PL_MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,7 @@ enum file_type {
|
||||||
FILE_IGNORE,
|
FILE_IGNORE,
|
||||||
FILE_REGULAR,
|
FILE_REGULAR,
|
||||||
FILE_PLAYLIST,
|
FILE_PLAYLIST,
|
||||||
|
FILE_SMARTPL,
|
||||||
FILE_ITUNES,
|
FILE_ITUNES,
|
||||||
FILE_ARTWORK,
|
FILE_ARTWORK,
|
||||||
FILE_CTRL_REMOTE,
|
FILE_CTRL_REMOTE,
|
||||||
|
@ -317,6 +318,9 @@ file_type_get(const char *path) {
|
||||||
if ((strcasecmp(ext, ".m3u") == 0) || (strcasecmp(ext, ".pls") == 0))
|
if ((strcasecmp(ext, ".m3u") == 0) || (strcasecmp(ext, ".pls") == 0))
|
||||||
return FILE_PLAYLIST;
|
return FILE_PLAYLIST;
|
||||||
|
|
||||||
|
if (strcasecmp(ext, ".smartpl") == 0)
|
||||||
|
return FILE_SMARTPL;
|
||||||
|
|
||||||
if (artwork_file_is_artwork(filename))
|
if (artwork_file_is_artwork(filename))
|
||||||
return FILE_ARTWORK;
|
return FILE_ARTWORK;
|
||||||
|
|
||||||
|
@ -533,7 +537,7 @@ fixup_tags(struct media_file_info *mfi)
|
||||||
/* Handle TV shows, try to present prettier metadata */
|
/* Handle TV shows, try to present prettier metadata */
|
||||||
if (mfi->tv_series_name && strlen(mfi->tv_series_name) != 0)
|
if (mfi->tv_series_name && strlen(mfi->tv_series_name) != 0)
|
||||||
{
|
{
|
||||||
mfi->media_kind = 64; /* tv show */
|
mfi->media_kind = MEDIA_KIND_TVSHOW; /* tv show */
|
||||||
|
|
||||||
/* Default to artist = series_name */
|
/* Default to artist = series_name */
|
||||||
if (mfi->artist && strlen(mfi->artist) == 0)
|
if (mfi->artist && strlen(mfi->artist) == 0)
|
||||||
|
@ -604,7 +608,7 @@ fixup_tags(struct media_file_info *mfi)
|
||||||
mfi->album_artist_sort = strdup("");
|
mfi->album_artist_sort = strdup("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mfi->media_kind == 4) /* Podcast */
|
else if (mfi->media_kind == MEDIA_KIND_PODCAST) /* Podcast */
|
||||||
{
|
{
|
||||||
if (mfi->album_artist)
|
if (mfi->album_artist)
|
||||||
free(mfi->album_artist);
|
free(mfi->album_artist);
|
||||||
|
@ -687,12 +691,12 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
|
||||||
|
|
||||||
if (type & F_SCAN_TYPE_FILE)
|
if (type & F_SCAN_TYPE_FILE)
|
||||||
{
|
{
|
||||||
mfi->data_kind = 0; /* real file */
|
mfi->data_kind = DATA_KIND_FILE; /* real file */
|
||||||
ret = scan_metadata_ffmpeg(path, mfi);
|
ret = scan_metadata_ffmpeg(path, mfi);
|
||||||
}
|
}
|
||||||
else if (type & F_SCAN_TYPE_URL)
|
else if (type & F_SCAN_TYPE_URL)
|
||||||
{
|
{
|
||||||
mfi->data_kind = 1; /* url/stream */
|
mfi->data_kind = DATA_KIND_URL; /* url/stream */
|
||||||
ret = scan_metadata_ffmpeg(path, mfi);
|
ret = scan_metadata_ffmpeg(path, mfi);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
|
@ -705,12 +709,12 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
|
||||||
}
|
}
|
||||||
else if (type & F_SCAN_TYPE_SPOTIFY)
|
else if (type & F_SCAN_TYPE_SPOTIFY)
|
||||||
{
|
{
|
||||||
mfi->data_kind = 2; /* iTunes has no spotify data kind, but we use 2 */
|
mfi->data_kind = DATA_KIND_SPOTIFY; /* iTunes has no spotify data kind, but we use 2 */
|
||||||
ret = mfi->artist && mfi->album && mfi->title;
|
ret = mfi->artist && mfi->album && mfi->title;
|
||||||
}
|
}
|
||||||
else if (type & F_SCAN_TYPE_PIPE)
|
else if (type & F_SCAN_TYPE_PIPE)
|
||||||
{
|
{
|
||||||
mfi->data_kind = 3; /* iTunes has no pipe data kind, but we use 3 */
|
mfi->data_kind = DATA_KIND_PIPE; /* iTunes has no pipe data kind, but we use 3 */
|
||||||
mfi->type = strdup("wav");
|
mfi->type = strdup("wav");
|
||||||
mfi->codectype = strdup("wav");
|
mfi->codectype = strdup("wav");
|
||||||
mfi->description = strdup("PCM16 pipe");
|
mfi->description = strdup("PCM16 pipe");
|
||||||
|
@ -731,14 +735,14 @@ filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct
|
||||||
if (type & F_SCAN_TYPE_COMPILATION)
|
if (type & F_SCAN_TYPE_COMPILATION)
|
||||||
mfi->compilation = 1;
|
mfi->compilation = 1;
|
||||||
if (type & F_SCAN_TYPE_PODCAST)
|
if (type & F_SCAN_TYPE_PODCAST)
|
||||||
mfi->media_kind = 4; /* podcast */
|
mfi->media_kind = MEDIA_KIND_PODCAST; /* podcast */
|
||||||
if (type & F_SCAN_TYPE_AUDIOBOOK)
|
if (type & F_SCAN_TYPE_AUDIOBOOK)
|
||||||
mfi->media_kind = 8; /* audiobook */
|
mfi->media_kind = MEDIA_KIND_AUDIOBOOK; /* audiobook */
|
||||||
|
|
||||||
if (!mfi->item_kind)
|
if (!mfi->item_kind)
|
||||||
mfi->item_kind = 2; /* music */
|
mfi->item_kind = 2; /* music */
|
||||||
if (!mfi->media_kind)
|
if (!mfi->media_kind)
|
||||||
mfi->media_kind = 1; /* music */
|
mfi->media_kind = MEDIA_KIND_MUSIC; /* music */
|
||||||
|
|
||||||
unicode_fixup_mfi(mfi);
|
unicode_fixup_mfi(mfi);
|
||||||
|
|
||||||
|
@ -867,6 +871,11 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags)
|
||||||
process_playlist(file, mtime);
|
process_playlist(file, mtime);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case FILE_SMARTPL:
|
||||||
|
DPRINTF(E_DBG, L_SCAN, "Smart playlist file: %s\n", file);
|
||||||
|
scan_smartpl(file, mtime);
|
||||||
|
break;
|
||||||
|
|
||||||
case FILE_ARTWORK:
|
case FILE_ARTWORK:
|
||||||
DPRINTF(E_DBG, L_SCAN, "Artwork file: %s\n", file);
|
DPRINTF(E_DBG, L_SCAN, "Artwork file: %s\n", file);
|
||||||
cache_artwork_ping(file, mtime);
|
cache_artwork_ping(file, mtime);
|
||||||
|
|
|
@ -31,6 +31,9 @@ scan_metadata_icy(char *url, struct media_file_info *mfi);
|
||||||
void
|
void
|
||||||
scan_playlist(char *file, time_t mtime);
|
scan_playlist(char *file, time_t mtime);
|
||||||
|
|
||||||
|
void
|
||||||
|
scan_smartpl(char *file, time_t mtime);
|
||||||
|
|
||||||
#ifdef ITUNES
|
#ifdef ITUNES
|
||||||
void
|
void
|
||||||
scan_itunes_itml(char *file);
|
scan_itunes_itml(char *file);
|
||||||
|
|
|
@ -346,14 +346,14 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
|
||||||
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3)
|
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3)
|
||||||
# ifndef HAVE_FFMPEG
|
# ifndef HAVE_FFMPEG
|
||||||
// Without this, libav is slow to probe some internet streams
|
// Without this, libav is slow to probe some internet streams
|
||||||
if (mfi->data_kind == 1)
|
if (mfi->data_kind == DATA_KIND_URL)
|
||||||
{
|
{
|
||||||
ctx = avformat_alloc_context();
|
ctx = avformat_alloc_context();
|
||||||
ctx->probesize = 64000;
|
ctx->probesize = 64000;
|
||||||
}
|
}
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
if (mfi->data_kind == 1)
|
if (mfi->data_kind == DATA_KIND_URL)
|
||||||
{
|
{
|
||||||
free(path);
|
free(path);
|
||||||
ret = http_stream_setup(&path, file);
|
ret = http_stream_setup(&path, file);
|
||||||
|
@ -493,7 +493,7 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
|
||||||
DPRINTF(E_DBG, L_SCAN, "Duration %d ms, bitrate %d kbps\n", mfi->song_length, mfi->bitrate);
|
DPRINTF(E_DBG, L_SCAN, "Duration %d ms, bitrate %d kbps\n", mfi->song_length, mfi->bitrate);
|
||||||
|
|
||||||
/* Try to extract ICY metadata if url/stream */
|
/* Try to extract ICY metadata if url/stream */
|
||||||
if (mfi->data_kind == 1)
|
if (mfi->data_kind == DATA_KIND_URL)
|
||||||
{
|
{
|
||||||
icy_metadata = http_icy_metadata_get(ctx, 0);
|
icy_metadata = http_icy_metadata_get(ctx, 0);
|
||||||
if (icy_metadata && icy_metadata->name)
|
if (icy_metadata && icy_metadata->name)
|
||||||
|
@ -753,12 +753,12 @@ scan_metadata_ffmpeg(char *file, struct media_file_info *mfi)
|
||||||
if (mfi->media_kind == 10)
|
if (mfi->media_kind == 10)
|
||||||
{
|
{
|
||||||
/* I have no idea why this is, but iTunes reports a media kind of 64 for stik==10 (?!) */
|
/* I have no idea why this is, but iTunes reports a media kind of 64 for stik==10 (?!) */
|
||||||
mfi->media_kind = 64;
|
mfi->media_kind = MEDIA_KIND_TVSHOW;
|
||||||
}
|
}
|
||||||
/* Unspecified video files are "Movies", media_kind 2 */
|
/* Unspecified video files are "Movies", media_kind 2 */
|
||||||
else if (mfi->has_video == 1)
|
else if (mfi->has_video == 1)
|
||||||
{
|
{
|
||||||
mfi->media_kind = 2;
|
mfi->media_kind = MEDIA_KIND_MOVIE;
|
||||||
}
|
}
|
||||||
|
|
||||||
skip_extract:
|
skip_extract:
|
||||||
|
|
|
@ -474,7 +474,7 @@ process_track_file(plist_t trk)
|
||||||
ret = get_dictval_bool_from_key(trk, "Podcast", &boolean);
|
ret = get_dictval_bool_from_key(trk, "Podcast", &boolean);
|
||||||
if ((ret == 0) && boolean)
|
if ((ret == 0) && boolean)
|
||||||
{
|
{
|
||||||
mfi->media_kind = 4;
|
mfi->media_kind = MEDIA_KIND_PODCAST;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Don't let album_artist set to "Unknown artist" if we've
|
/* Don't let album_artist set to "Unknown artist" if we've
|
||||||
|
@ -768,6 +768,7 @@ process_pls(plist_t playlists, char *file)
|
||||||
}
|
}
|
||||||
memset(pli, 0, sizeof(struct playlist_info));
|
memset(pli, 0, sizeof(struct playlist_info));
|
||||||
|
|
||||||
|
pli->type = PL_PLAIN;
|
||||||
pli->title = strdup(name);
|
pli->title = strdup(name);
|
||||||
pli->path = strdup(file);
|
pli->path = strdup(file);
|
||||||
snprintf(virtual_path, PATH_MAX, "/file:%s", file);
|
snprintf(virtual_path, PATH_MAX, "/file:%s", file);
|
||||||
|
|
|
@ -152,6 +152,8 @@ scan_playlist(char *file, time_t mtime)
|
||||||
|
|
||||||
memset(pli, 0, sizeof(struct playlist_info));
|
memset(pli, 0, sizeof(struct playlist_info));
|
||||||
|
|
||||||
|
pli->type = PL_PLAIN;
|
||||||
|
|
||||||
/* Get only the basename, to be used as the playlist title */
|
/* Get only the basename, to be used as the playlist title */
|
||||||
ptr = strrchr(filename, '.');
|
ptr = strrchr(filename, '.');
|
||||||
if (ptr)
|
if (ptr)
|
||||||
|
|
|
@ -0,0 +1,216 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Christian Meffert <christian.meffert@googlemail.com>
|
||||||
|
*
|
||||||
|
* 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 <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
#include "db.h"
|
||||||
|
#include "filescanner.h"
|
||||||
|
#include "misc.h"
|
||||||
|
|
||||||
|
#include "SMARTPLLexer.h"
|
||||||
|
#include "SMARTPLParser.h"
|
||||||
|
#include "SMARTPL2SQL.h"
|
||||||
|
|
||||||
|
static int
|
||||||
|
smartpl_parse_file(const char *file, struct playlist_info *pli)
|
||||||
|
{
|
||||||
|
pANTLR3_INPUT_STREAM input;
|
||||||
|
pSMARTPLLexer lxr;
|
||||||
|
pANTLR3_COMMON_TOKEN_STREAM tstream;
|
||||||
|
pSMARTPLParser psr;
|
||||||
|
SMARTPLParser_playlist_return qtree;
|
||||||
|
pANTLR3_COMMON_TREE_NODE_STREAM nodes;
|
||||||
|
pSMARTPL2SQL sqlconv;
|
||||||
|
SMARTPL2SQL_playlist_return plreturn;
|
||||||
|
|
||||||
|
input = antlr3AsciiFileStreamNew((pANTLR3_UINT8) file);
|
||||||
|
|
||||||
|
// The input will be created successfully, providing that there is enough memory and the file exists etc
|
||||||
|
if (input == NULL)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Unable to open smart playlist file %s\n", file);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
lxr = SMARTPLLexerNew(input);
|
||||||
|
|
||||||
|
// Need to check for errors
|
||||||
|
if (lxr == NULL)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Could not create SMARTPL lexer\n");
|
||||||
|
goto lxr_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
tstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lxr));
|
||||||
|
|
||||||
|
if (tstream == NULL)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Could not create SMARTPL token stream\n");
|
||||||
|
goto tkstream_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, now that we have our lexer constructed, we can create the parser
|
||||||
|
psr = SMARTPLParserNew(tstream); // CParserNew is generated by ANTLR3
|
||||||
|
|
||||||
|
if (tstream == NULL)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Could not create SMARTPL parser\n");
|
||||||
|
goto psr_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
qtree = psr->playlist(psr);
|
||||||
|
|
||||||
|
/* Check for parser errors */
|
||||||
|
if (psr->pParser->rec->state->errorCount > 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "SMARTPL query parser terminated with %d errors\n", psr->pParser->rec->state->errorCount);
|
||||||
|
goto psr_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_SCAN, "SMARTPL query AST:\n\t%s\n", qtree.tree->toStringTree(qtree.tree)->chars);
|
||||||
|
|
||||||
|
nodes = antlr3CommonTreeNodeStreamNewTree(qtree.tree, ANTLR3_SIZE_HINT);
|
||||||
|
if (!nodes)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Could not create node stream\n");
|
||||||
|
goto psr_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlconv = SMARTPL2SQLNew(nodes);
|
||||||
|
if (!sqlconv)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Could not create SQL converter\n");
|
||||||
|
goto sql_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
plreturn = sqlconv->playlist(sqlconv);
|
||||||
|
|
||||||
|
/* Check for tree parser errors */
|
||||||
|
if (sqlconv->pTreeParser->rec->state->errorCount > 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "SMARTPL query tree parser terminated with %d errors\n", sqlconv->pTreeParser->rec->state->errorCount);
|
||||||
|
goto sql_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plreturn.title && plreturn.query)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DBG, L_SCAN, "SMARTPL SQL title '%s' query: -%s-\n", plreturn.title->chars, plreturn.query->chars);
|
||||||
|
|
||||||
|
if (pli->title)
|
||||||
|
free(pli->title);
|
||||||
|
pli->title = strdup((char *)plreturn.title->chars);
|
||||||
|
|
||||||
|
if (pli->query)
|
||||||
|
free(pli->query);
|
||||||
|
pli->query = strdup((char *)plreturn.query->chars);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Invalid SMARTPL query\n");
|
||||||
|
|
||||||
|
sql_error:
|
||||||
|
sqlconv->free(sqlconv);
|
||||||
|
sql_fail:
|
||||||
|
nodes->free(nodes);
|
||||||
|
psr_error:
|
||||||
|
psr->free(psr);
|
||||||
|
psr_fail:
|
||||||
|
tstream->free(tstream);
|
||||||
|
tkstream_fail:
|
||||||
|
lxr->free(lxr);
|
||||||
|
lxr_fail:
|
||||||
|
input->close(input);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
scan_smartpl(char *file, time_t mtime)
|
||||||
|
{
|
||||||
|
struct playlist_info *pli;
|
||||||
|
int pl_id;
|
||||||
|
char virtual_path[PATH_MAX];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Fetch or create playlist */
|
||||||
|
pli = db_pl_fetch_bypath(file);
|
||||||
|
if (!pli)
|
||||||
|
{
|
||||||
|
pli = (struct playlist_info *) malloc(sizeof(struct playlist_info));
|
||||||
|
if (!pli)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Out of memory\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(pli, 0, sizeof(struct playlist_info));
|
||||||
|
|
||||||
|
pli->path = strdup(file);
|
||||||
|
snprintf(virtual_path, PATH_MAX, "/file:%s", file);
|
||||||
|
pli->virtual_path = strdup(virtual_path);
|
||||||
|
pli->type = PL_SMART;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
pl_id = pli->id;
|
||||||
|
|
||||||
|
ret = smartpl_parse_file(file, pli);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Error parsing smart playlist '%s'\n", file);
|
||||||
|
|
||||||
|
free_pli(pli, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pli->id)
|
||||||
|
{
|
||||||
|
ret = db_pl_update(pli);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret = db_pl_add(pli, &pl_id);
|
||||||
|
}
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_SCAN, "Error adding smart playlist '%s'\n", file);
|
||||||
|
|
||||||
|
free_pli(pli, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DPRINTF(E_INFO, L_SCAN, "Added smart playlist as id %d\n", pl_id);
|
||||||
|
|
||||||
|
free_pli(pli, 0);
|
||||||
|
|
||||||
|
DPRINTF(E_INFO, L_SCAN, "Done processing smart playlist\n");
|
||||||
|
}
|
|
@ -415,7 +415,7 @@ httpd_stream_file(struct evhttp_request *req, int id)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mfi->data_kind != 0)
|
if (mfi->data_kind != DATA_KIND_FILE)
|
||||||
{
|
{
|
||||||
evhttp_send_error(req, 500, "Cannot stream radio station");
|
evhttp_send_error(req, 500, "Cannot stream radio station");
|
||||||
|
|
||||||
|
|
|
@ -1685,8 +1685,8 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char **
|
||||||
if (!cfg_radiopl && (database != DAAP_DB_RADIO) && (plstreams > 0) && (plstreams == plitems))
|
if (!cfg_radiopl && (database != DAAP_DB_RADIO) && (plstreams > 0) && (plstreams == plitems))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Don't add empty Smart playlists */
|
/* Don't add empty Special playlists */
|
||||||
if ((plid > 1) && (plitems == 0) && (pltype == PL_SMART))
|
if ((plid > 1) && (plitems == 0) && (pltype == PL_SPECIAL))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
npls++;
|
npls++;
|
||||||
|
@ -1700,14 +1700,20 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char **
|
||||||
if (dfm == &dfm_dmap_mimc)
|
if (dfm == &dfm_dmap_mimc)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* com.apple.itunes.smart-playlist - type = PL_SMART AND id != 1 */
|
/* Add field "com.apple.itunes.smart-playlist" for special and smart playlists
|
||||||
|
(excluding the special playlist for "library" with id = 1) */
|
||||||
if (dfm == &dfm_dmap_aeSP)
|
if (dfm == &dfm_dmap_aeSP)
|
||||||
{
|
{
|
||||||
if ((pltype == PL_SMART) && (plid != 1))
|
if ((pltype == PL_SMART) || ((pltype == PL_SPECIAL) && (plid != 1)))
|
||||||
|
{
|
||||||
|
dmap_add_char(playlist, "aeSP", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add field "com.apple.itunes.special-playlist" for special playlists
|
||||||
|
(excluding the special playlist for "library" with id = 1) */
|
||||||
|
if ((pltype == PL_SPECIAL) && (plid != 1))
|
||||||
{
|
{
|
||||||
int32_t aePS = 0;
|
int32_t aePS = 0;
|
||||||
dmap_add_char(playlist, "aeSP", 1);
|
|
||||||
|
|
||||||
ret = safe_atoi32(dbpli.special_id, &aePS);
|
ret = safe_atoi32(dbpli.special_id, &aePS);
|
||||||
if ((ret == 0) && (aePS > 0))
|
if ((ret == 0) && (aePS > 0))
|
||||||
dmap_add_char(playlist, "aePS", aePS);
|
dmap_add_char(playlist, "aePS", aePS);
|
||||||
|
|
|
@ -590,7 +590,7 @@ scrobble(struct lastfm_command *cmd)
|
||||||
goto noscrobble;
|
goto noscrobble;
|
||||||
|
|
||||||
// Don't scrobble non-music and radio stations
|
// Don't scrobble non-music and radio stations
|
||||||
if ((mfi->media_kind != 1) || (mfi->data_kind == 1))
|
if ((mfi->media_kind != MEDIA_KIND_MUSIC) || (mfi->data_kind == DATA_KIND_URL))
|
||||||
goto noscrobble;
|
goto noscrobble;
|
||||||
|
|
||||||
// Don't scrobble songs with unknown artist
|
// Don't scrobble songs with unknown artist
|
||||||
|
|
|
@ -1373,7 +1373,7 @@ source_open(struct player_source *ps, int no_md)
|
||||||
// Setup the source type responsible for getting the audio
|
// Setup the source type responsible for getting the audio
|
||||||
switch (mfi->data_kind)
|
switch (mfi->data_kind)
|
||||||
{
|
{
|
||||||
case 1:
|
case DATA_KIND_URL:
|
||||||
ps->type = SOURCE_HTTP;
|
ps->type = SOURCE_HTTP;
|
||||||
|
|
||||||
ret = http_stream_setup(&url, mfi->path);
|
ret = http_stream_setup(&url, mfi->path);
|
||||||
|
@ -1386,7 +1386,7 @@ source_open(struct player_source *ps, int no_md)
|
||||||
ret = transcode_setup(&ps->ctx, mfi, NULL, 0);
|
ret = transcode_setup(&ps->ctx, mfi, NULL, 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case DATA_KIND_SPOTIFY:
|
||||||
ps->type = SOURCE_SPOTIFY;
|
ps->type = SOURCE_SPOTIFY;
|
||||||
#ifdef HAVE_SPOTIFY_H
|
#ifdef HAVE_SPOTIFY_H
|
||||||
ret = spotify_playback_play(mfi);
|
ret = spotify_playback_play(mfi);
|
||||||
|
@ -1395,7 +1395,7 @@ source_open(struct player_source *ps, int no_md)
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case DATA_KIND_PIPE:
|
||||||
ps->type = SOURCE_PIPE;
|
ps->type = SOURCE_PIPE;
|
||||||
ret = pipe_setup(mfi);
|
ret = pipe_setup(mfi);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -690,6 +690,7 @@ spotify_playlist_save(sp_playlist *pl)
|
||||||
|
|
||||||
memset(pli, 0, sizeof(struct playlist_info));
|
memset(pli, 0, sizeof(struct playlist_info));
|
||||||
|
|
||||||
|
pli->type = PL_PLAIN;
|
||||||
pli->title = strdup(name);
|
pli->title = strdup(name);
|
||||||
pli->path = strdup(url);
|
pli->path = strdup(url);
|
||||||
pli->virtual_path = strdup(virtual_path);
|
pli->virtual_path = strdup(virtual_path);
|
||||||
|
|
|
@ -507,13 +507,13 @@ transcode_setup(struct transcode_ctx **nctx, struct media_file_info *mfi, off_t
|
||||||
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3)
|
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3)
|
||||||
# ifndef HAVE_FFMPEG
|
# ifndef HAVE_FFMPEG
|
||||||
// Without this, libav is slow to probe some internet streams, which leads to RAOP timeouts
|
// Without this, libav is slow to probe some internet streams, which leads to RAOP timeouts
|
||||||
if (mfi->data_kind == 1)
|
if (mfi->data_kind == DATA_KIND_URL)
|
||||||
{
|
{
|
||||||
ctx->fmtctx = avformat_alloc_context();
|
ctx->fmtctx = avformat_alloc_context();
|
||||||
ctx->fmtctx->probesize = 64000;
|
ctx->fmtctx->probesize = 64000;
|
||||||
}
|
}
|
||||||
# endif
|
# endif
|
||||||
if (mfi->data_kind == 1)
|
if (mfi->data_kind == DATA_KIND_URL)
|
||||||
av_dict_set(&options, "icy", "1", 0);
|
av_dict_set(&options, "icy", "1", 0);
|
||||||
|
|
||||||
ret = avformat_open_input(&ctx->fmtctx, mfi->path, NULL, &options);
|
ret = avformat_open_input(&ctx->fmtctx, mfi->path, NULL, &options);
|
||||||
|
|
Loading…
Reference in New Issue