websiteWebsite
sima MPD_sima
Automagically add title to mpd playlist

 

Browse the code

Differences between 113 and 114 on /.
Number of edited files: 10 (3 added, 0 deleted and 7 modified)
Author: kaliko
Log message: merge latest trunk commits, add user DB among others.
Date: 2009-07-02 18:38:22

Added file(s) Deleted file(s) Modified file(s)

 

Old New Code
68 68
details.
69 69
.RE
70 70

                                        
71  
.I $XDG_DATA_HOME/mpd_sima/add_history.pkl
  71
.I $XDG_DATA_HOME/mpd_sima/history.pkl
72 72
.RS
73 73

                                        
74 74
History of added tracks created when \fBhistory\fP is set to \fBtrue\fP in
75 75

                                        

 

Old New Code
1 1
sima v0.3.0
2 2

                                        
  3
 * manage user's own database for similar artists trought a new conf file
  4
   user_db_cfg. Refer to examples for syntax.
3 5
 * check playlist queue to add tracks, not only history.
4 6
 * Rename history history.pkl
5 7
 * add new command line options
6 8

                                        

 

Old New Code
6 6
 * Now create configuration file looking for available environment variables
7 7
   MPD_HOST and MPD_PORT.
8 8
 * add --version|-v and --help|-h command line options
  9
 * possibility to add user's similar artist to add to last.fm replies
9 10

                                        
10  

                                    
11 11

                                        

 

Old New Code
1  
 - MPD_SIMA -
2 1

                                        
  2
  - MPD_SIMA -
3 3

                                        
  4

                                    
  5
N.B.
  6

                                    
  7
MPD_sima has been written for python2.5 and is yet to be tested with
  8
python2.6. 
  9

                                    
  10

                                    
4 11
 INSTALL
5 12
-=-=-=-=-
6 13
No installation nor compilation necessary.
7  
Make sure to satisfy dependency (currently python-mpd only)
  14
Make sure to satisfy dependency (currently python-mpd only).
8 15

                                        
9  
 0/ Install mpd python library (python-mpd package).
  16
 0. Install mpd python library (python-mpd package).
10 17
 
11  
 1/ Launching MPD_sima will create a default conf file in $XDG_CONFIG_HOME
  18
 1. Launching MPD_sima will create a default conf file in $XDG_CONFIG_HOME
12 19
    
13 20
    If you set up environment variables MPD_HOST and MPD_PORT then
14 21
    MPD_sima will use them to create the conf file.
15 22
    Running MPD on localhost should also be fine for MPD_sima even
16  
    without no environment set up. You can get to the next step.
  23
    without no environment set up (provided you do not use password).
17 24

                                        
18 25
    If your MPD serveur is not localhost or protected with a password
19 26
    set it up in $XDG_CONFIG_HOME/mpd_sima/mpd_sima.cfg
20 27
    Usually $XDG_CONFIG_HOME is set to "$HOME/.config/".
21 28
 
22  
 2/ Pray Gaïa and A.M. Turing.
  29
 2. Pray Gaïa and A.M. Turing.
23 30

                                        
24  
 3/ Run MPD_sima by launching either the shell or python script.
  31
 3. Run MPD_sima by launching either the shell or python script.
25 32
    The shell script is just provided as an example, functional though.
26  
    Shell
  33
    * Shell
27 34
    		"sh ./launch.sh"
28  
    Python
  35
    * Python
29 36
    		"python ./src/mpd_sima.py --pid=/path/to/my_PID_file.pid"
  37
		or
  38
    		"python2.5 ./src/mpd_sima.py --pid=/path/to/my_PID_file.pid"
30 39

                                        
31  
 4/ Lay back and enjoy.
  40
 4. Lay back and enjoy.
32 41
 
33  
 5/ Well… refer to next section to report bugs, request features…
  42
 5. Well… refer to next section to report bugs, request features…
34 43

                                        
35 44

                                        
36 45
 CONFIGURATION
55 64
You should not need special configuration for regular use apart from
56 65
MPD server host/port/password settings.  But if you want more control
57 66
over MPD_sima behaviour such as how many track to add to the playlist or
58  
to be able to recover your history of added tracks between two different
59  
sessions (ie execution of the code), have a look to the next section
60  
DESCRIPTION - advanced use.
  67
to be able to recover your history of played tracks between two different
  68
sessions, have a look to the next section "DESCRIPTION - advanced use".
61 69

                                        
62 70

                                        
63 71
 DEPENDENCIES
78 86

                                        
79 87
 DESCRIPTION - advanced use
80 88
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
81  
The commented file "mpd_sima.cfg.example" (in examples directory)
  89
The commented file <mpd_sima.cfg.example> (in examples directory)
82 90
present all defaults values for options in both mandatory and optional
83  
sections on the configuration file. Optional sections gives you some
84  
more control over MPD_sima core behaviour. Here follows a description of
85  
these options.
  91
sections on the configuration file. Optional sections give you some
  92
more control over MPD_sima core behaviour.  Here follows a description
  93
of these options.
86 94

                                        
87 95

                                        
88 96
MPD_sima will add tracks in your playlist following suggestions from
92 100
be defined in the configuration file (mpd_sima.cfg) otherwise the
93 101
default values will be used:
94 102

                                        
95  
track_to_add = 2
  103
track_to_add = 1
96 104
queue_length = 2
97 105

                                        
98 106
MPD_sima is controlling MPD's state regularly, the "main_loop_time"
121 129
 efficient way to refresh only modified entries (cf. audioscrobbler
122 130
 library). Hence, once created, the cache will remain unchanged.
123 131

                                        
  132
USER DB
  133
The user cache allows you to associate similar artists to a specific
  134
artist following you own musical taste. File syntax is described in
  135
examples directory.
  136
In order to activate it, set up "user_db = True" and fill user_db.cfg to
  137
suits your needs. For this file specific syntax please refer to the file
  138
<user_db.cfg.example> (in examples directory).
124 139

                                        
125  
CACHE & HISTORY files.
  140
CACHE, HISTORY & USER_DB files.
126 141
Cache and history files are saved to standard directory $XDG_DATA_HOME,
127 142
if not set the default $HOME/.local/share/ is used.
128 143
History and cache file are saved only if respective option are set to
129 144
true and when you stop MPD_sima properly sending it a SIGTERM (15) or a
130 145
SIGINT (2, a.k.a.  interrupt from keyboard / CTRL-C).
131  
History : $XDG_DATA_HOME/mpd_sima/add_history.pkl
132  
Cache   : $XDG_DATA_HOME/mpd_sima/similar_cache.pkl
  146
History    : $XDG_DATA_HOME/mpd_sima/history.pkl
  147
Cache      : $XDG_DATA_HOME/mpd_sima/similar_cache.pkl
  148
User_DB    : $XDG_CONFIG_HOME/mpd_sima/usr_db.cfg
133 149

                                        
134 150

                                        
135 151
 FEED BACK
136 152

                                        

 

Old New Code
1  
#######################################################################
  1
########################################################################
2 2
#
3 3
# If you need special settings, rename this file as mpd_sima.cfg within
4 4
# your $XDG_CONFIG_HOME (default is $HOME/.config/mpd_sima/)
13 13

                                        
14 14
########################## MANDATORY SECTION ##########################
15 15
#
16  
#
  16
## MPD ##
17 17
[MPD]
  18
## HOST # type: string
18 19
host = localhost
  20
## PORT # type: decimal string
19 21
port = 6600
  22
## PASSWORD # type: boolean
20 23
# password is optional, a warning will raise though
21 24
password = false
22 25

                                        
30 33
# But well you got bored of the way MPD_sima is behaving, well then go ahead
31 34
# play with it :)
32 35

                                        
33  
#LOGGING FACILITY
  36
## LOGGING FACILITY ##
34 37
[log]
35 38
# message are logged to console
36 39
#
37  
## VERBOSITY:
  40
## VERBOSITY # type: string (within a list)
38 41
# verbosity in : debug, info, warning, error
39 42
# default if not specify is "info"
40  
# *DEBUG LEVEL WILL PRINT OUT YOUR PASSWORD*
  43
# *DEBUG LEVEL WILL PRINT OUT YOUR MPD PASSWORD*
41 44
verbosity = info
42 45
##
43 46

                                        
44 47
#CORE
45 48
[sima]
46  
# These settings deal with  MPD_sima core behaviour.
  49
# These settings deal with MPD_sima core behaviour.
47 50
#
48  
## HISTORY:
  51
## USER_DB # type: boolean
  52
# Load user database to find similar artists
  53
# User DB is loaded from $XDG_CONFIG_HOME/mpd_sima/user_db.cfg
  54
user_db = False
  55

                                    
  56
## HISTORY # type: boolean
49 57
# keep history between sessions (pickle/unpickle history)
50 58
# to forget history remove $XDG_DATA_HOME/mpd_sima/add_history.pkl
51 59
# value: True | False
52 60
history = False
53  
## CACHE:
  61

                                    
  62
## CACHE # type: boolean
54 63
# …
55 64
# to reset cache remove $XDG_DATA_HOME/mpd_sima/similar_cache.pkl
56  
# value: True | False
57 65
cache = False
58  
##
  66

                                    
  67
## QUEUE_LENGTH # type: decimal string
59 68
# Missing Description…
60 69
queue_length = 2
61  
track_to_add = 2
62  
##
63  
#
64  
## MAIN_LOOP_TIME
65  
# time pause between two checks in seconds 
66  
# (less than 2~3 seconds IS useless)
  70

                                    
  71
## TRACK_TO_ADD # type: decimal string
  72
# Missing Description…
  73
track_to_add = 1
  74

                                    
  75
## MAIN_LOOP_TIME # type: decimal string
  76
# time pause in seconds between two checks
  77
# (less than 2~3 seconds IS USELESS)
67 78
main_loop_time =  4
68 79

                                        
69 80
#
70 81

                                        

 

Old New Code
29 29

                                        
30 30
from __future__ import (with_statement)
31 31

                                        
32  
__version__ = "0.3.0"
  32
__version__ = "0.3.0beta"
33 33
__revison__ = "$Revision$"[11:-2]
34 34
__author__ = "$Author$"
35 35
__date__ = (
50 50
from lib.audioscrobbler import (AudioScrobblerQuery,
51 51
        AudioScrobblerConnectionError)
52 52
import lib.mpdConnector as mpdConnector
53  
from utils.config import ConfMan
  53
from utils.config import (ConfMan, load_cache, load_usr_cache, CacheError)
54 54
from utils.leven import levenshtein_ratio
55 55
from utils.logutil import (sima_logger, LEVELS)
56 56
from utils.startopt import StartOpt
172 172
            track = artist_nolist(track)
173 173
            if not self.is_inqueue(track) and \
174 174
               track not in self.tracks_to_add:
175  
                #if self.addhist(track) and not self.is_inqueue(track):
176  
                #if self.addhist(track):
177 175
                if not self.is_inhist(track):
178 176
                    selected_track = track
179 177
                    break
240 238
        mpd_status = self.mpd_connection.status()
241 239
        queue_lgh = self.sima_config.getint('sima', 'queue_length')
242 240
        mfile = self.mpd_connection.currentsong().get('file', None)
243  
        # playlist_length - mpd_track_position - 1 > queue_lgh:
  241
        #print int(mpd_status['playlistlength']) - int(mpd_status['song']) - 1 > queue_lgh
244 242
        if (int(mpd_status['playlistlength']) - int(mpd_status['song']) - 1 >
245 243
            int(queue_lgh)):
246 244
            return False
286 284
            return False
287 285

                                        
288 286
        if not title:
289  
            SIMA_LOGGER.warning('No title tag set for "%s".' %
290  
                    basename(mfile))
  287
            SIMA_LOGGER.warning('No title tag set for "%s".' % basename(mfile))
  288

                                    
291 289
        # MPD state
292 290
        #self.current_title = title
293 291
        #self.current_artist = artist
331 329
            unplayed_track = self.extract_unplayed(mpd_find)
332 330
            if not unplayed_track:
333 331
                SIMA_LOGGER.info('Unable to find title to add'+
334  
                                    ' for %s.' % artist)
  332
                                 ' for "%s".' % artist)
335 333
                if artist in self.mpd_add_history:
336 334
                    SIMA_LOGGER.info(
337 335
                            'History already gathers %i titles for %s.' %
388 386
            pickle.dump(cache_stamp, cache_fd, -1)#}}}
389 387

                                        
390 388

                                        
391  
def load_cache(cache_file):#{{{
392  
    """Load cache from file."""
393  
    similar_cache = {}
394  
    cache_stamp = None
395  
    if isfile(cache_file):
396  
        with open(cache_file, 'rb') as cache_fd:
397  
            SIMA_LOGGER.info('Loading cache from:  %s' % cache_file)
398  
            similar_cache = pickle.load(cache_fd)
399  
            cache_stamp = pickle.load(cache_fd)
400  
            SIMA_LOGGER.info('%d artists in cache' % len(similar_cache))
401  

                                    
402  
    return similar_cache, cache_stamp#}}}
403  

                                    
404  

                                    
405 389
def similar_artist(s_artist):#{{{
406 390
    """
407  
    Retrieve similar artists on last.fm server.
  391
    Retrieve similar artists on last.fm server and from user DB if available.
  392
    <s_artist> is MPD current artist
408 393
    """
409 394
    as_artist_names = []
  395
    # Look for AS ressources
410 396
    if s_artist in CACHE:
411 397
        as_artist_names = CACHE[s_artist]
412 398
        SIMA_LOGGER.debug('"%s" already in cache, not requesting last.fm.' %
413 399
                s_artist)
414 400
    else:
415  
        as_artists = AudioScrobblerQuery(artist=s_artist)
  401
        try:
  402
            as_artists = AudioScrobblerQuery(artist=s_artist)
  403
        except AudioScrobblerConnectionError, as_error:
  404
            SIMA_LOGGER.warning('Audioscrobbler server replies: %s…' %
  405
                                as_error)
  406
            return False
  407

                                    
416 408
        for artist in as_artists.similar():
417 409
            as_artist_names.append(str(artist.name))
418 410
        CACHE[s_artist] = as_artist_names
419 411

                                        
  412
    # Look for user ressources
  413
    #
  414
    if s_artist in USR_DB:
  415
        as_artist_names = USR_DB[s_artist] + as_artist_names
  416

                                    
420 417
    return as_artist_names#}}}
421 418

                                        
422 419

                                        
479 476
    SIMA_LOGGER = sima_logger()
480 477
    StartOpt(SIMA_LOGGER,(__version__, __revison__, __date__))
481 478

                                        
482  

                                    
483 479
    # Configuration manager Object
484 480
    CONF_MANAGER = ConfMan(SIMA_LOGGER)
485 481
    CONFIG = CONF_MANAGER.control_conf()
486 482

                                        
487 483
    # Load Cache
  484
    CACHE = {}
488 485
    if CONFIG.getboolean('sima', 'cache'):
  486
        SIMA_LOGGER.info('Loading cache from:  %s' % CONF_MANAGER.similar_cache)
489 487
        CACHE, CACHE_STAMP = load_cache(CONF_MANAGER.similar_cache)
490  
    else:
491  
        CACHE = {}
  488
        SIMA_LOGGER.info('%d artists in cache' % len(CACHE))
492 489

                                        
  490
    # Load user cache
  491
    USR_DB = {}
  492
    # USR_DB is a dict object, same format as CACHE
  493
    #
  494
    # USR_DB = {'artist O': ['similar 00', 'similar 01'],
  495
    #           'artist 1': ['similar 01', 'similar 02']
  496
    #           }
  497
    # similar artist name 0 = None
  498
    # similar artist name 1 = None
  499
    # …
  500
    # then USR_DB.items('artist') returns: 
  501
    # [ ('similar artist name 0',''), ('similar artist name 0','')]
  502
    #
  503
    if CONFIG.getboolean('sima', 'user_db'):
  504
        SIMA_LOGGER.info('Loading user DB from:  %s' % CONF_MANAGER.user_cache)
  505
        try:
  506
            USR_DB = load_usr_cache(CONF_MANAGER.user_cache)
  507
            #print [ art[0] for art in USR_DB.items('essai') ]
  508
            print USR_DB
  509
            SIMA_LOGGER.info('%d artists in user DB' % len(USR_DB))
  510
        except CacheError, cache_err:
  511
            SIMA_LOGGER.warning(cache_err)
  512

                                    
493 513
    # Logging settings
494 514
    # Define the logger following user conf
495 515
    #  default log level is INFO.
559 579
        except KeyboardInterrupt, int_err:
560 580
            if CONFIG.getboolean('sima', 'history'):
561 581
                SIMA.writehist(CONF_MANAGER.history_file)
562  
            if CONFIG.getboolean('sima', 'cache'):
  582
            if CONFIG.getboolean('sima', 'cache') and CACHE:
563 583
                writecache(CONF_MANAGER.similar_cache)
564 584
                if CACHE_STAMP:
565 585
                    DELTA = date.today() - CACHE_STAMP['DATE']
566 586

                                        

 

Old New Code
27 27

                                        
28 28
# IMPORTS {{{
29 29
from __future__ import with_statement
  30
import ConfigParser
  31
import pickle
30 32
import sys
31  
from stat import (S_IMODE, ST_MODE, S_IRWXO, S_IRWXG)
  33

                                    
  34
from ConfigParser import (MissingSectionHeaderError, ParsingError)
32 35
from os import (makedirs, environ, stat, chmod, rename)
33 36
from os.path import (join, isdir, isfile, expanduser)
34  
import ConfigParser
35  
from ConfigParser import (MissingSectionHeaderError, ParsingError)
  37
from stat import (S_IMODE, ST_MODE, S_IRWXO, S_IRWXG)
36 38
#}}}
37 39

                                        
38 40
#DEFAULTS{{{
39 41
CONF_FILE = 'mpd_sima.cfg'
  42
USR_DB = 'user_db.cfg'
40 43

                                        
41 44
# Mandatory user options
42 45
M_SECTIONS = ['MPD']
51 54
        'sima': {
52 55
            'history': "false",
53 56
            'cache': "false",
  57
            'user_db': "false",
54 58
            'queue_length': "2",
55 59
            'track_to_add': "1",
56 60
            'main_loop_time': "4"},
59 63

                                        
60 64
#}}}
61 65

                                        
  66
class CacheError(Exception):
  67
    def __init__(self, value):
  68
        self.value = value
  69
    def __str__(self):
  70
        return repr(self.value)
62 71

                                        
  72

                                    
  73
#CONFIG FONCTIONS{{{
  74

                                    
  75
def load_cache(cache_file):#{{{
  76
    """Load cache from file."""
  77
    similar_cache = {}
  78
    cache_stamp = None
  79
    if isfile(cache_file):
  80
        with open(cache_file, 'rb') as cache_fd:
  81
            similar_cache = pickle.load(cache_fd)
  82
            cache_stamp = pickle.load(cache_fd)
  83

                                    
  84
    return similar_cache, cache_stamp#}}}
  85

                                    
  86
def load_usr_cache(cache_file):
  87
    """Add User Cache to regular cache"""
  88
    config = ConfigParser.SafeConfigParser()
  89
    try:
  90
        config.read(cache_file)
  91
    except (MissingSectionHeaderError, ParsingError), err_conf:
  92
        raise CacheError(err_conf)
  93

                                    
  94
    usr_db = {}
  95
    # Convert config to dict object to match CACHE type
  96
    for artist in config.sections():
  97
        usr_db[artist] = [ art[0] for art in config.items(artist) ]
  98

                                    
  99
    return usr_db
  100
#}}}
  101

                                    
63 102
#CONFIG MANAGER CLASS{{{
64 103

                                        
65 104

                                        
72 111
        self.conf_file = None
73 112
        self.history_file = None
74 113
        self.similar_cache = None
  114
        self.user_cache = None
75 115
        self.config = None
76 116
        self.defaults = DEFAULT_CONF
77 117
        self.sima_logger = logger
79 119
        self.control_mod()#}}}
80 120

                                        
81 121
    def mpd_environ(self):#{{{
  122
        """
  123
        Use MPD_HOST/MPD_PORT if set as environment variables.
  124
        """
82 125
        mpd_host_pass = environ.get('MPD_HOST').split('@')
83 126
        if mpd_host_pass[0]:
84 127
            self.sima_logger.info('MPD_HOST set to "%s", setting up conf file.' %
226 269
        self.similar_cache = join(data_dir, "similar_cache.pkl")
227 270

                                        
228 271
        self.conf_file = join(conf_dir, CONF_FILE)
  272
        self.user_cache = join(conf_dir, USR_DB)
229 273

                                        
230  
        self.sima_logger.info("Loading configuration from %s" % self.conf_file)
  274
        self.sima_logger.info("Loading configuration from:  %s" % self.conf_file)
231 275
        # Load conf
232 276
        if not isfile(self.conf_file):
233 277
            self.config_write_default()
234 278