diff --git a/modules/difm/__init__.py b/modules/difm/__init__.py
new file mode 100644
index 00000000..add930c0
--- /dev/null
+++ b/modules/difm/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Roger Philibert
+#
+# This file is part of weboob.
+#
+# weboob is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# weboob 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with weboob. If not, see .
+
+
+from .backend import DIFMBackend
+
+
+__all__ = ['DIFMBackend']
diff --git a/modules/difm/backend.py b/modules/difm/backend.py
new file mode 100644
index 00000000..bff6d002
--- /dev/null
+++ b/modules/difm/backend.py
@@ -0,0 +1,141 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Roger Philibert
+#
+# This file is part of weboob.
+#
+# weboob is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# weboob 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with weboob. If not, see .
+
+
+from weboob.capabilities.radio import ICapRadio, Radio, Stream, Emission
+from weboob.capabilities.collection import ICapCollection
+from weboob.tools.backend import BaseBackend
+from weboob.tools.browser import StandardBrowser
+from weboob.tools.misc import to_unicode
+
+
+__all__ = ['DIFMBackend']
+
+
+class DIFMBackend(BaseBackend, ICapRadio, ICapCollection):
+ NAME = 'difm'
+ MAINTAINER = u'Roger Philibert'
+ EMAIL = 'roger.philibert@gmail.com'
+ VERSION = '0.h'
+ DESCRIPTION = u'Digitally Imported web radio'
+ LICENSE = 'AGPLv3+'
+ BROWSER = StandardBrowser
+
+ # http://tobiass.eu/api
+ # http://mpd.wikia.com/wiki/Hack:di.fm-playlists
+ FORMATS = {('AAC', 64): 'public1', ('AAC', 32): 'public2', ('MP3', 96): 'public3', ('WMA', 40): 'public5'}
+
+ def __init__(self, *a, **kw):
+ super(DIFMBackend, self).__init__(*a, **kw)
+ self.RADIOS = {}
+
+ def _get_playlist_url(self, radio_key, format):
+ return 'http://listen.di.fm/%s/%s.pls' % (self.FORMATS[format], radio_key)
+
+ def _get_info_url(self, radio_key):
+ self._fetch_radio_list()
+
+ return 'http://api.audioaddict.com/v1/di/track_history/channel/%s' % self.RADIOS[radio_key]['id']
+
+ def create_default_browser(self):
+ return self.create_browser(parser='json')
+
+ def _fetch_radio_list(self):
+ if not self.RADIOS:
+ document = self.browser.location('http://listen.di.fm/public3')
+ for info in document:
+ key = info['key']
+ self.RADIOS[key] = {}
+ self.RADIOS[key]['id'] = info['id']
+ self.RADIOS[key]['description'] = info['description']
+ self.RADIOS[key]['name'] = info['name']
+
+ return self.RADIOS
+
+ def iter_radios_search(self, pattern):
+ self._fetch_radio_list()
+
+ pattern = pattern.lower()
+ for radio_key in self.RADIOS:
+ radio_dict = self.RADIOS[radio_key]
+ if pattern in radio_dict['name'].lower() or pattern in radio_dict['description'].lower():
+ yield self.get_radio(radio_key)
+
+ def iter_resources(self, objs, split_path):
+ self._fetch_radio_list()
+
+ if Radio in objs:
+ self._restrict_level(split_path)
+
+ for radio_key in self.RADIOS:
+ yield self.get_radio(radio_key)
+
+ def get_current(self, radio_key):
+ document = self.browser.location(self._get_info_url(radio_key))
+
+ artist = title = 'Advertising'
+ for last in document:
+ if 'ad' in last:
+ continue
+
+ artist = last.get('artist', '') or 'Unknown'
+ title = last.get('title', '') or 'Unknown'
+
+ artist = to_unicode(artist.strip())
+ title = to_unicode(title.strip())
+ break
+
+ return artist, title
+
+ def get_radio(self, radio):
+ self._fetch_radio_list()
+
+ if not isinstance(radio, Radio):
+ radio = Radio(radio)
+
+ if not radio.id in self.RADIOS:
+ return None
+
+ radio_dict = self.RADIOS[radio.id]
+ radio.title = radio_dict['name']
+ radio.description = radio_dict['description']
+
+ artist, title = self.get_current(radio.id)
+ current = Emission(0)
+ current.artist = artist
+ current.title = title
+ radio.current = current
+
+ radio.streams = []
+ for format in self.FORMATS:
+ stream = Stream(0)
+ stream.title = u'%s %skbps' % format
+ stream.url = self._get_playlist_url(radio.id, format)
+ radio.streams.append(stream)
+ return radio
+
+ def fill_radio(self, radio, fields):
+ if 'current' in fields:
+ if not radio.current:
+ radio.current = Emission(0)
+ radio.current.artist, radio.current.title = self.get_current(radio.id)
+ return radio
+
+ OBJECTS = {Radio: fill_radio}
+
diff --git a/modules/difm/favicon.png b/modules/difm/favicon.png
new file mode 100644
index 00000000..38af0bb3
Binary files /dev/null and b/modules/difm/favicon.png differ