From: Dr.Best Date: Sat, 27 Feb 2010 12:52:53 +0000 (+0000) Subject: Merlin Music Player and iDream - initial checkin X-Git-Url: http://code.vuplus.com/gitweb/?a=commitdiff_plain;h=4c065c064c447581619ff68302f494b7fb61bd23;p=vuplus_dvbapp-plugin Merlin Music Player and iDream - initial checkin --- diff --git a/Makefile.am b/Makefile.am index 7f2f94b..ace41bd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,6 +25,7 @@ SUBDIRS = \ letterbox \ logomanager \ mediadownloader \ + merlinmusicplayer \ meteoitalia \ mosaic \ moviecut \ diff --git a/configure.ac b/configure.ac index 5a60f95..eacec0f 100644 --- a/configure.ac +++ b/configure.ac @@ -14,6 +14,8 @@ if test x"${have_e2_includes}" = "xyes"; then TUXBOX_APPS_DVB TUXBOX_APPS_LIB_PKGCONFIG(ENIGMA2,enigma2) TUXBOX_APPS_LIB_PKGCONFIG(SIGC,sigc++-1.2) + _TUXBOX_APPS_LIB_PKGCONFIG_OPTIONAL(GSTREAMER,gstreamer,HAVE_GSTREAMER) + _TUXBOX_APPS_LIB_PKGCONFIG_OPTIONAL(GSTREAMERPBUTILS,gstreamer-pbutils,HAVE_GSTSTREAMERPBUTILS) AC_DEFINE(HAVE_E2_INCLUDES, 1,[Define if enigm2 includes are available]) CXXFLAGS="$CXXFLAGS -fno-rtti -fno-exceptions" LDFLAGS="$LDFLAGS -pthread $PYTHON_LDFLAGS" @@ -143,6 +145,12 @@ mediadownloader/meta/Makefile mediadownloader/po/Makefile mediadownloader/src/Makefile +merlinmusicplayer/Makefile +merlinmusicplayer/src/Makefile +merlinmusicplayer/src/images/Makefile +merlinmusicplayer/src/merlinmp3player/Makefile + + meteoitalia/Makefile meteoitalia/meta/Makefile meteoitalia/src/Makefile diff --git a/merlinmusicplayer/CONTROL/control b/merlinmusicplayer/CONTROL/control new file mode 100644 index 0000000..6a77bc2 --- /dev/null +++ b/merlinmusicplayer/CONTROL/control @@ -0,0 +1,10 @@ +Package: enigma2-plugin-extensions-merlinmusicplayer +Version: 1.1 +Description: Merlin Music Player and iDream (music management) +Architecture: mipsel +Section: extra +Priority: optional +Maintainer: Dr. Best +Homepage: http://www.dreambox-tools.info +Depends: enigma2(>2.6git20090615), twisted-web, python-mutagen, python-sqlite3 +Source: http://enigma2-plugins.schwerkraft.elitedvb.net/ diff --git a/merlinmusicplayer/Makefile.am b/merlinmusicplayer/Makefile.am new file mode 100755 index 0000000..7a31bf0 --- /dev/null +++ b/merlinmusicplayer/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = meta src diff --git a/merlinmusicplayer/meta/Makefile.am b/merlinmusicplayer/meta/Makefile.am new file mode 100755 index 0000000..c9f8622 --- /dev/null +++ b/merlinmusicplayer/meta/Makefile.am @@ -0,0 +1,5 @@ +installdir = $(datadir)/meta/ + +dist_install_DATA = plugin_merlinmusicplayer.xml + +EXTRA_DIST = merlinmusicplayer.jpg diff --git a/merlinmusicplayer/meta/merlinmusicplayer.jpg b/merlinmusicplayer/meta/merlinmusicplayer.jpg new file mode 100644 index 0000000..99371e7 Binary files /dev/null and b/merlinmusicplayer/meta/merlinmusicplayer.jpg differ diff --git a/merlinmusicplayer/meta/plugin_merlinmusicplayer.xml b/merlinmusicplayer/meta/plugin_merlinmusicplayer.xml new file mode 100755 index 0000000..381744b --- /dev/null +++ b/merlinmusicplayer/meta/plugin_merlinmusicplayer.xml @@ -0,0 +1,24 @@ + + + + + + Dr.Best + Merlin Music Player + enigma2-plugin-extensions-merlinmusicplayer + Merlin Music Player and iDream + Manage your music files in a database, play it with Merlin Music Player + + + + Dr.Best + Partnerbox + enigma2-plugin-extensions-merlinmusicplayer + Merlin Music Player und iDream + Datenbankverwaltung für Musikdateien, Abspielen mit Merlin Music Player + + + + + + diff --git a/merlinmusicplayer/src/Makefile.am b/merlinmusicplayer/src/Makefile.am new file mode 100755 index 0000000..d2813d0 --- /dev/null +++ b/merlinmusicplayer/src/Makefile.am @@ -0,0 +1,7 @@ +SUBDIRS = images merlinmp3player + +installdir = /usr/lib/enigma2/python/Plugins/Extensions/MerlinMusicPlayer + +install_PYTHON = *.py merlinmp3player/merlinmp3player.so + +install_DATA = *.png diff --git a/merlinmusicplayer/src/MerlinMusicPlayer.png b/merlinmusicplayer/src/MerlinMusicPlayer.png new file mode 100644 index 0000000..ad519dc Binary files /dev/null and b/merlinmusicplayer/src/MerlinMusicPlayer.png differ diff --git a/merlinmusicplayer/src/__init__.py b/merlinmusicplayer/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/merlinmusicplayer/src/iDream.png b/merlinmusicplayer/src/iDream.png new file mode 100644 index 0000000..cb0fa32 Binary files /dev/null and b/merlinmusicplayer/src/iDream.png differ diff --git a/merlinmusicplayer/src/images/Makefile.am b/merlinmusicplayer/src/images/Makefile.am new file mode 100755 index 0000000..4a4b218 --- /dev/null +++ b/merlinmusicplayer/src/images/Makefile.am @@ -0,0 +1,3 @@ +installdir = /usr/lib/enigma2/python/Plugins/Extensions/MerlinMusicPlayer/images + +install_DATA = *.png diff --git a/merlinmusicplayer/src/images/dvr.png b/merlinmusicplayer/src/images/dvr.png new file mode 100644 index 0000000..58f0a31 Binary files /dev/null and b/merlinmusicplayer/src/images/dvr.png differ diff --git a/merlinmusicplayer/src/images/dvr_pau.png b/merlinmusicplayer/src/images/dvr_pau.png new file mode 100644 index 0000000..0aac511 Binary files /dev/null and b/merlinmusicplayer/src/images/dvr_pau.png differ diff --git a/merlinmusicplayer/src/images/dvr_pl.png b/merlinmusicplayer/src/images/dvr_pl.png new file mode 100644 index 0000000..79ac9d1 Binary files /dev/null and b/merlinmusicplayer/src/images/dvr_pl.png differ diff --git a/merlinmusicplayer/src/images/dvr_rep.png b/merlinmusicplayer/src/images/dvr_rep.png new file mode 100644 index 0000000..f3f7ddb Binary files /dev/null and b/merlinmusicplayer/src/images/dvr_rep.png differ diff --git a/merlinmusicplayer/src/images/dvr_shuf.png b/merlinmusicplayer/src/images/dvr_shuf.png new file mode 100644 index 0000000..570c1a8 Binary files /dev/null and b/merlinmusicplayer/src/images/dvr_shuf.png differ diff --git a/merlinmusicplayer/src/images/mmp3p.png b/merlinmusicplayer/src/images/mmp3p.png new file mode 100644 index 0000000..30d271b Binary files /dev/null and b/merlinmusicplayer/src/images/mmp3p.png differ diff --git a/merlinmusicplayer/src/images/mmp3pHD.png b/merlinmusicplayer/src/images/mmp3pHD.png new file mode 100644 index 0000000..bc45882 Binary files /dev/null and b/merlinmusicplayer/src/images/mmp3pHD.png differ diff --git a/merlinmusicplayer/src/images/mmpborderHD.png b/merlinmusicplayer/src/images/mmpborderHD.png new file mode 100644 index 0000000..74e4650 Binary files /dev/null and b/merlinmusicplayer/src/images/mmpborderHD.png differ diff --git a/merlinmusicplayer/src/images/no_coverArt.png b/merlinmusicplayer/src/images/no_coverArt.png new file mode 100644 index 0000000..debc902 Binary files /dev/null and b/merlinmusicplayer/src/images/no_coverArt.png differ diff --git a/merlinmusicplayer/src/images/placeholder1.png b/merlinmusicplayer/src/images/placeholder1.png new file mode 100644 index 0000000..87f9e56 Binary files /dev/null and b/merlinmusicplayer/src/images/placeholder1.png differ diff --git a/merlinmusicplayer/src/images/progressbar.png b/merlinmusicplayer/src/images/progressbar.png new file mode 100644 index 0000000..c1a9d19 Binary files /dev/null and b/merlinmusicplayer/src/images/progressbar.png differ diff --git a/merlinmusicplayer/src/merlinmp3player/Makefile.am b/merlinmusicplayer/src/merlinmp3player/Makefile.am new file mode 100644 index 0000000..7da3d32 --- /dev/null +++ b/merlinmusicplayer/src/merlinmp3player/Makefile.am @@ -0,0 +1,15 @@ +OBJS = merlinmp3player.cpp + +-include $(OBJS:.cpp=.d) + +merlinmp3player.so: merlinmp3player.h + + + $(CXX) $(CPPFLAGS) -MD $(CXXFLAGS) $(ENIGMA2_CFLAGS) $(SIGC_CFLAGS) $(GSTREAMER_CFLAGS) $(GSTREAMERPBUTILS_CFLAGS) $(GSTREAMER_LDFLAGS) $(GSTREAMERPBUTILS_LDFLAGS) \ + $(PYTHON_CPPFLAGS) $(DEFS) -I$(top_srcdir)/include -Wall -lgstbase-0.10 -W $(OBJS) -shared -fPIC -Wl,-soname,merlinmp3player.so -o merlinmp3player.so \ + $(LDFLAGS) + +all: merlinmp3player.so + +CLEANFILES = merlinmp3player.so merlinmp3player.d + diff --git a/merlinmusicplayer/src/merlinmp3player/merlinmp3player.cpp b/merlinmusicplayer/src/merlinmp3player/merlinmp3player.cpp new file mode 100644 index 0000000..0f25e41 --- /dev/null +++ b/merlinmusicplayer/src/merlinmp3player/merlinmp3player.cpp @@ -0,0 +1,404 @@ +/* + MerlinMP3Player E2 + + (c) 2010 by Dr. Best + Support: www.dreambox-tools.info + +*/ + +#include +#include +#include +#include +#include "merlinmp3player.h" +#include +#include +#include +#include + +// eServiceFactoryMerlinMP3Player + +eServiceFactoryMerlinMP3Player::eServiceFactoryMerlinMP3Player() +{ + ePtr sc; + + eServiceCenter::getPrivInstance(sc); + if (sc) + { + std::list extensions; + extensions.push_back("mp3"); + sc->addServiceFactory(eServiceFactoryMerlinMP3Player::id, this, extensions); + } + m_service_info = new eStaticServiceMP3Info(); + +} + +eServiceFactoryMerlinMP3Player::~eServiceFactoryMerlinMP3Player() +{ + ePtr sc; + + eServiceCenter::getPrivInstance(sc); + if (sc) + sc->removeServiceFactory(eServiceFactoryMerlinMP3Player::id); +} + +DEFINE_REF(eServiceFactoryMerlinMP3Player) + + // iServiceHandler +RESULT eServiceFactoryMerlinMP3Player::play(const eServiceReference &ref, ePtr &ptr) +{ + // check resources... + ptr = new eServiceMerlinMP3Player(ref); + return 0; +} + +RESULT eServiceFactoryMerlinMP3Player::record(const eServiceReference &ref, ePtr &ptr) +{ + ptr=0; + return -1; +} + +RESULT eServiceFactoryMerlinMP3Player::list(const eServiceReference &, ePtr &ptr) +{ + ptr=0; + return -1; +} + +RESULT eServiceFactoryMerlinMP3Player::info(const eServiceReference &ref, ePtr &ptr) +{ + ptr = m_service_info; + return 0; +} + +RESULT eServiceFactoryMerlinMP3Player::offlineOperations(const eServiceReference &, ePtr &ptr) +{ + ptr = 0; + return -1; +} + +DEFINE_REF(eStaticServiceMP3Info) + +eStaticServiceMP3Info::eStaticServiceMP3Info() +{ + // nothing to to here... +} + +RESULT eStaticServiceMP3Info::getName(const eServiceReference &ref, std::string &name) +{ + size_t last = ref.path.rfind('/'); + if (last != std::string::npos) + name = ref.path.substr(last+1); + else + name = ref.path; + return 0; +} + +int eStaticServiceMP3Info::getLength(const eServiceReference &ref) +{ + return -1; +} + +// eServiceMerlinMP3Player + +eServiceMerlinMP3Player::eServiceMerlinMP3Player(eServiceReference ref): m_ref(ref), m_pump(eApp, 1) +{ + m_filename = m_ref.path.c_str(); + CONNECT(m_pump.recv_msg, eServiceMerlinMP3Player::gstPoll); + m_state = stIdle; + eDebug("eServiceMerlinMP3Player construct!"); + + GstElement *sink; + GstElement *source; + GstElement *decoder; + + m_gst_pipeline = gst_pipeline_new ("audio-player"); + if (!m_gst_pipeline) + eWarning("failed to create pipeline"); + + source = gst_element_factory_make ("filesrc", "file reader"); + decoder = gst_element_factory_make ("mad", "MP3 decoder"); + sink = gst_element_factory_make ("alsasink", "ALSA output"); + if (m_gst_pipeline && source && decoder && sink) + { + g_object_set (G_OBJECT (source), "location", m_filename.c_str(), NULL); + gst_bin_add_many (GST_BIN (m_gst_pipeline), source, decoder, sink, NULL); + gst_element_link_many (source, decoder, sink, NULL); + gst_bus_set_sync_handler(gst_pipeline_get_bus (GST_PIPELINE (m_gst_pipeline)), gstBusSyncHandler, this); + gst_element_set_state (m_gst_pipeline, GST_STATE_PLAYING); + } + else + { + if (m_gst_pipeline) + gst_object_unref(GST_OBJECT(m_gst_pipeline)); + if (source) + gst_object_unref(GST_OBJECT(source)); + if (decoder) + gst_object_unref(GST_OBJECT(decoder)); + if (sink) + gst_object_unref(GST_OBJECT(sink)); + eDebug("no playing...!"); + } + eDebug("eServiceMerlinMP3Player::using gstreamer with location=%s", m_filename.c_str()); +} + +eServiceMerlinMP3Player::~eServiceMerlinMP3Player() +{ + if (m_state == stRunning) + stop(); + + if (m_gst_pipeline) + { + gst_object_unref (GST_OBJECT (m_gst_pipeline)); + eDebug("eServiceMerlinMP3Player destruct!"); + } +} + +DEFINE_REF(eServiceMerlinMP3Player); + +RESULT eServiceMerlinMP3Player::connectEvent(const Slot2 &event, ePtr &connection) +{ + connection = new eConnection((iPlayableService*)this, m_event.connect(event)); + return 0; +} + +RESULT eServiceMerlinMP3Player::start() +{ + assert(m_state == stIdle); + + m_state = stRunning; + if (m_gst_pipeline) + { + eDebug("eServiceMerlinMP3Player::starting pipeline"); + gst_element_set_state (m_gst_pipeline, GST_STATE_PLAYING); + } + m_event(this, evStart); + return 0; +} + +RESULT eServiceMerlinMP3Player::stop() +{ + assert(m_state != stIdle); + if (m_state == stStopped) + return -1; + eDebug("eServiceMerlinMP3Player::stop %s", m_filename.c_str()); + gst_element_set_state(m_gst_pipeline, GST_STATE_NULL); + m_state = stStopped; + return 0; +} + +RESULT eServiceMerlinMP3Player::setTarget(int target) +{ + return -1; +} + +RESULT eServiceMerlinMP3Player::pause(ePtr &ptr) +{ + ptr=this; + return 0; +} + +RESULT eServiceMerlinMP3Player::setSlowMotion(int ratio) +{ + return -1; +} + +RESULT eServiceMerlinMP3Player::setFastForward(int ratio) +{ + return -1; +} + + // iPausableService +RESULT eServiceMerlinMP3Player::pause() +{ + if (!m_gst_pipeline) + return -1; + gst_element_set_state(m_gst_pipeline, GST_STATE_PAUSED); + return 0; +} + +RESULT eServiceMerlinMP3Player::unpause() +{ + if (!m_gst_pipeline) + return -1; + gst_element_set_state(m_gst_pipeline, GST_STATE_PLAYING); + return 0; +} + + /* iSeekableService */ +RESULT eServiceMerlinMP3Player::seek(ePtr &ptr) +{ + ptr = this; + return 0; +} + +RESULT eServiceMerlinMP3Player::getLength(pts_t &pts) +{ + if (!m_gst_pipeline) + return -1; + if (m_state != stRunning) + return -1; + + GstFormat fmt = GST_FORMAT_TIME; + gint64 len; + + if (!gst_element_query_duration(m_gst_pipeline, &fmt, &len)) + return -1; + + /* len is in nanoseconds. we have 90 000 pts per second. */ + + pts = len / 11111; + return 0; +} + +RESULT eServiceMerlinMP3Player::seekTo(pts_t to) +{ + if (!m_gst_pipeline) + return -1; + + /* convert pts to nanoseconds */ + gint64 time_nanoseconds = to * 11111LL; + if (!gst_element_seek (m_gst_pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, time_nanoseconds, + GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) + { + eDebug("eServiceMerlinMP3Player::SEEK failed"); + return -1; + } + return 0; +} + +RESULT eServiceMerlinMP3Player::seekRelative(int direction, pts_t to) +{ + if (!m_gst_pipeline) + return -1; + + pause(); + + pts_t ppos; + getPlayPosition(ppos); + ppos += to * direction; + if (ppos < 0) + ppos = 0; + seekTo(ppos); + + unpause(); + + return 0; +} + +RESULT eServiceMerlinMP3Player::getPlayPosition(pts_t &pts) +{ + if (!m_gst_pipeline) + return -1; + if (m_state != stRunning) + return -1; + + GstFormat fmt = GST_FORMAT_TIME; + gint64 len; + + if (!gst_element_query_position(m_gst_pipeline, &fmt, &len)) + return -1; + + /* len is in nanoseconds. we have 90 000 pts per second. */ + pts = len / 11111; + return 0; +} + +RESULT eServiceMerlinMP3Player::setTrickmode(int trick) +{ + /* trickmode currently doesn't make any sense for us. */ + return -1; +} + +RESULT eServiceMerlinMP3Player::isCurrentlySeekable() +{ + return 1; +} + +RESULT eServiceMerlinMP3Player::info(ePtr&i) +{ + i = this; + return 0; +} + +RESULT eServiceMerlinMP3Player::getName(std::string &name) +{ + name = m_filename; + size_t n = name.rfind('/'); + if (n != std::string::npos) + name = name.substr(n + 1); + return 0; +} + +int eServiceMerlinMP3Player::getInfo(int w) +{ + return resNA; +} + +std::string eServiceMerlinMP3Player::getInfoString(int w) +{ + return ""; +} + +void eServiceMerlinMP3Player::gstBusCall(GstBus *bus, GstMessage *msg) +{ + switch (GST_MESSAGE_TYPE (msg)) + { + case GST_MESSAGE_EOS: + m_event((iPlayableService*)this, evEOF); + break; + case GST_MESSAGE_STATE_CHANGED: + { + if(GST_MESSAGE_SRC(msg) != GST_OBJECT(m_gst_pipeline)) + break; + GstState old_state, new_state; + gst_message_parse_state_changed(msg, &old_state, &new_state, NULL); + if(old_state == new_state) + break; + eDebug("eServiceMerlinMP3Player::state transition %s -> %s", gst_element_state_get_name(old_state), gst_element_state_get_name(new_state)); + break; + } + case GST_MESSAGE_ERROR: + { + gchar *debug; + GError *err; + gst_message_parse_error (msg, &err, &debug); + g_free (debug); + eWarning("Gstreamer error: %s", err->message); + g_error_free(err); + break; + } + default: + break; + } +} + +GstBusSyncReply eServiceMerlinMP3Player::gstBusSyncHandler(GstBus *bus, GstMessage *message, gpointer user_data) +{ + eServiceMerlinMP3Player *_this = (eServiceMerlinMP3Player*)user_data; + _this->m_pump.send(1); + /* wake */ + return GST_BUS_PASS; +} + +void eServiceMerlinMP3Player::gstPoll(const int&) +{ + usleep(1); + GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (m_gst_pipeline)); + GstMessage *message; + while ((message = gst_bus_pop (bus))) + { + gstBusCall(bus, message); + gst_message_unref (message); + } +} + + +eAutoInitPtr init_eServiceFactoryMerlinMP3Player(eAutoInitNumbers::service+1, "eServiceFactoryMerlinMP3Player"); + +PyMODINIT_FUNC +initmerlinmp3player(void) +{ + Py_InitModule("merlinmp3player", NULL); +} + diff --git a/merlinmusicplayer/src/merlinmp3player/merlinmp3player.h b/merlinmusicplayer/src/merlinmp3player/merlinmp3player.h new file mode 100644 index 0000000..693cbbb --- /dev/null +++ b/merlinmusicplayer/src/merlinmp3player/merlinmp3player.h @@ -0,0 +1,105 @@ +#include +#include +#include + +class eStaticServiceMP3Info; + +class eServiceFactoryMerlinMP3Player: public iServiceHandler +{ +DECLARE_REF(eServiceFactoryMerlinMP3Player); +public: + eServiceFactoryMerlinMP3Player(); + virtual ~eServiceFactoryMerlinMP3Player(); + enum { id = 0x1014 }; + + // iServiceHandler + RESULT play(const eServiceReference &, ePtr &ptr); + RESULT record(const eServiceReference &, ePtr &ptr); + RESULT list(const eServiceReference &, ePtr &ptr); + RESULT info(const eServiceReference &, ePtr &ptr); + RESULT offlineOperations(const eServiceReference &, ePtr &ptr); +private: + ePtr m_service_info; +}; + +class eStaticServiceMP3Info: public iStaticServiceInformation +{ + DECLARE_REF(eStaticServiceMP3Info); + friend class eServiceFactoryMerlinMP3Player; + eStaticServiceMP3Info(); +public: + RESULT getName(const eServiceReference &ref, std::string &name); + int getLength(const eServiceReference &ref); +}; + +typedef struct _GstElement GstElement; + +class eServiceMerlinMP3Player: public iPlayableService, public iPauseableService, + public iServiceInformation, public iSeekableService, public Object +{ +DECLARE_REF(eServiceMerlinMP3Player); +public: + virtual ~eServiceMerlinMP3Player(); + + // iPlayableService + RESULT connectEvent(const Slot2 &event, ePtr &connection); + RESULT start(); + RESULT stop(); + RESULT setTarget(int target); + + RESULT pause(ePtr &ptr); + RESULT setSlowMotion(int ratio); + RESULT setFastForward(int ratio); + + RESULT seek(ePtr &ptr); + // not implemented + RESULT audioChannel(ePtr &ptr) { ptr = 0; return 0; }; + RESULT audioTracks(ePtr &ptr) { ptr = 0; return 0; }; + RESULT frontendInfo(ePtr &ptr) { ptr = 0; return -1; }; + RESULT subServices(ePtr &ptr) { ptr = 0; return -1; }; + RESULT timeshift(ePtr &ptr) { ptr = 0; return -1; }; + RESULT cueSheet(ePtr &ptr) { ptr = 0; return -1; }; + RESULT subtitle(ePtr &ptr) { ptr = 0; return -1; }; + RESULT audioDelay(ePtr &ptr) { ptr = 0; return -1; }; + RESULT rdsDecoder(ePtr &ptr) { ptr = 0; return -1; }; + RESULT stream(ePtr &ptr) { ptr = 0; return -1; }; + RESULT streamed(ePtr &ptr) { ptr = 0; return -1; }; + RESULT keys(ePtr &ptr) { ptr = 0; return -1; }; + + // iPausableService + RESULT pause(); + RESULT unpause(); + + RESULT info(ePtr&); + + // iSeekableService + RESULT getLength(pts_t &SWIG_OUTPUT); + RESULT seekTo(pts_t to); + RESULT seekRelative(int direction, pts_t to); + RESULT getPlayPosition(pts_t &SWIG_OUTPUT); + RESULT setTrickmode(int trick); + RESULT isCurrentlySeekable(); + + // iServiceInformation + RESULT getName(std::string &name); + int getInfo(int w); + std::string getInfoString(int w); +private: + friend class eServiceFactoryMerlinMP3Player; + eServiceReference m_ref; + std::string m_filename; + eServiceMerlinMP3Player(eServiceReference ref); + Signal2 m_event; + enum + { + stIdle, stRunning, stStopped, + }; + int m_state; + GstElement *m_gst_pipeline; + eFixedMessagePump m_pump; + + void gstBusCall(GstBus *bus, GstMessage *msg); + static GstBusSyncReply gstBusSyncHandler(GstBus *bus, GstMessage *message, gpointer user_data); + void gstPoll(const int&); +}; + diff --git a/merlinmusicplayer/src/plugin.py b/merlinmusicplayer/src/plugin.py new file mode 100644 index 0000000..387b9aa --- /dev/null +++ b/merlinmusicplayer/src/plugin.py @@ -0,0 +1,2726 @@ +# +# Merlin Music Player E2 +# +# +# Coded by Dr.Best (c) 2010 +# Support: www.dreambox-tools.info +# +# This plugin is licensed under the Creative Commons +# Attribution-NonCommercial-ShareAlike 3.0 Unported +# License. To view a copy of this license, visit +# http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative +# Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. +# +# Alternatively, this plugin may be distributed and executed on hardware which +# is licensed by Dream Multimedia GmbH. + +# This plugin is NOT free software. It is open source, you are allowed to +# modify it (if you keep the license), but it may not be commercially +# distributed other than under the conditions noted above. +# + +from Plugins.Plugin import PluginDescriptor +from Screens.Screen import Screen +from Components.ActionMap import ActionMap, NumberActionMap +from Components.Label import Label +from enigma import RT_VALIGN_CENTER, RT_HALIGN_LEFT, RT_HALIGN_RIGHT, RT_HALIGN_CENTER, gFont, eListbox,ePoint, eListboxPythonMultiContent + +# merlin mp3 player +import merlinmp3player +ENIGMA_MERLINPLAYER_ID = 0x1014 + +from Components.FileList import FileList +from enigma import eServiceReference, eTimer +from os import path as os_path, mkdir as os_mkdir, listdir as os_listdir, walk as os_walk +from Components.ProgressBar import ProgressBar +from twisted.internet import reactor, defer +from twisted.web import client +from twisted.web.client import HTTPClientFactory, downloadPage +from enigma import getDesktop +from Screens.MessageBox import MessageBox +from Components.GUIComponent import GUIComponent +from enigma import ePicLoad +from xml.etree.cElementTree import fromstring as cet_fromstring +from urllib import quote +from Components.ScrollLabel import ScrollLabel +from Components.AVSwitch import AVSwitch +from Tools.Directories import fileExists, resolveFilename, SCOPE_CURRENT_SKIN +from Tools.LoadPixmap import LoadPixmap +from Components.Pixmap import Pixmap, MultiPixmap +from Components.ServicePosition import ServicePositionGauge +from Screens.InfoBarGenerics import InfoBarSeek, InfoBarNotifications +from Components.ServiceEventTracker import ServiceEventTracker, InfoBarBase +from enigma import iPlayableService, iServiceInformation +from Components.Sources.StaticText import StaticText +from Screens.ChoiceBox import ChoiceBox +from Screens.VirtualKeyBoard import VirtualKeyBoard +from Tools.BoundFunction import boundFunction +from sqlite3 import dbapi2 as sqlite +from mutagen.flac import FLAC +from mutagen.mp3 import MP3 +from mutagen.id3 import ID3 +from mutagen.easyid3 import EasyID3 +from mutagen.easymp4 import EasyMP4 +from mutagen.oggvorbis import OggVorbis +import datetime +from random import shuffle, randrange +from Components.config import config, ConfigSubsection, ConfigDirectory, ConfigYesNo, ConfigInteger, getConfigListEntry, configfile +from Components.ConfigList import ConfigListScreen +from Tools.HardwareInfo import HardwareInfo + + +config.plugins.merlinmusicplayer = ConfigSubsection() +config.plugins.merlinmusicplayer.hardwaredecoder = ConfigYesNo(default = True) +config.plugins.merlinmusicplayer.startlastsonglist = ConfigYesNo(default = True) +config.plugins.merlinmusicplayer.lastsonglistindex = ConfigInteger(-1) +config.plugins.merlinmusicplayer.databasepath = ConfigDirectory(default = "/hdd/") +config.plugins.merlinmusicplayer.usegoogleimage = ConfigYesNo(default = True) +config.plugins.merlinmusicplayer.googleimagepath = ConfigDirectory(default = "/hdd/") +config.plugins.merlinmusicplayer.usescreensaver = ConfigYesNo(default = True) +config.plugins.merlinmusicplayer.screensaverwait = ConfigInteger(1,limits = (1, 60)) +config.plugins.merlinmusicplayer.idreamextendedpluginlist = ConfigYesNo(default = True) +config.plugins.merlinmusicplayer.merlinmusicplayerextendedpluginlist = ConfigYesNo(default = True) +config.plugins.merlinmusicplayer.defaultfilebrowserpath = ConfigDirectory(default = "/hdd/") +config.plugins.merlinmusicplayer.rememberlastfilebrowserpath = ConfigYesNo(default = True) + +from enigma import ePythonMessagePump +from threading import Thread, Lock + +class ThreadQueue: + def __init__(self): + self.__list = [ ] + self.__lock = Lock() + + def push(self, val): + lock = self.__lock + lock.acquire() + self.__list.append(val) + lock.release() + + def pop(self): + lock = self.__lock + lock.acquire() + ret = self.__list.pop() + lock.release() + return ret + +THREAD_WORKING = 1 +THREAD_FINISHED = 2 + +class PathToDatabase(Thread): + def __init__(self): + Thread.__init__(self) + self.__running = False + self.__cancel = False + self.__path = None + self.__messages = ThreadQueue() + self.__messagePump = ePythonMessagePump() + + def __getMessagePump(self): + return self.__messagePump + + def __getMessageQueue(self): + return self.__messages + + def __getRunning(self): + return self.__running + + def Cancel(self): + self.__cancel = True + + MessagePump = property(__getMessagePump) + Message = property(__getMessageQueue) + isRunning = property(__getRunning) + + def Start(self, path): + if not self.__running: + self.__path = path + self.start() + + def run(self): + mp = self.__messagePump + self.__running = True + self.__cancel = False + if self.__path: + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + counter = 0 + for root, subFolders, files in os_walk(self.__path): + if self.__cancel: + break + for filename in files: + if self.__cancel: + break + audio, isAudio, title, genre,artist,album,tracknr,track,date,length,bitrate = getID3Tags(root,filename) + if audio: + # 1. Artist + artistID = -1 + cursor.execute("""SELECT artist_id FROM Artists WHERE artist = "%s";""" % (artist)) + row = cursor.fetchone() + if row is None: + cursor.execute("""INSERT INTO Artists (artist) VALUES("%s");""" % (artist)) + artistID = cursor.lastrowid + else: + artistID = row[0] + # 2. Album + albumID = -1 + cursor.execute("""SELECT album_id FROM Album WHERE album_text = "%s";""" % (album)) + row = cursor.fetchone() + if row is None: + cursor.execute("""INSERT INTO Album (album_text) VALUES("%s");""" % (album)) + albumID = cursor.lastrowid + else: + albumID = row[0] + + # 3. Genre + genreID = -1 + cursor.execute("""SELECT genre_id FROM Genre WHERE genre_text = "%s";""" % (genre)) + row = cursor.fetchone() + if row is None: + cursor.execute("""INSERT INTO Genre (genre_text) VALUES("%s");""" % (genre)) + genreID = cursor.lastrowid + else: + genreID = row[0] + + # 4. Songs + try: + cursor.execute("INSERT INTO Songs (filename,title,artist_id,album_id,genre_id,tracknumber, bitrate, length, track, date) VALUES(?,?,?,?,?,?,?,?,?,?);" , (os_path.join(root,filename),title,artistID,albumID,genreID, tracknr, bitrate, length, track, date)) + self.__messages.push((THREAD_WORKING, _("%s\n added to database") % os_path.join(root,filename))) + mp.send(0) + counter +=1 + except sqlite.IntegrityError: + self.__messages.push((THREAD_WORKING, _("%s\n already exists in database!") % os_path.join(root,filename))) + mp.send(0) + audio = None + if not self.__cancel: + connection.commit() + cursor.close() + connection.close() + if self.__cancel: + self.__messages.push((THREAD_FINISHED, _("Process aborted.\n 0 files added to database!\nPress OK to close.") )) + else: + self.__messages.push((THREAD_FINISHED, _("%d files added to database!\nPress OK to close." % counter))) + mp.send(0) + self.__running = False + Thread.__init__(self) + +pathToDatabase = PathToDatabase() + + +class iDreamAddToDatabase(Screen): + skin = """ + + + + + + + + """ + def __init__(self, session, initDir): + Screen.__init__(self, session) + self["actions"] = ActionMap(["WizardActions", "ColorActions"], + { + "back": self.cancel, + "green": self.green, + "red": self.cancel, + "ok": self.green, + + }, -1) + self["key_red"] = StaticText(_("Cancel")) + self["key_green"] = StaticText("Close") + self["output"] = Label() + self.onClose.append(self.__onClose) + pathToDatabase.MessagePump.recv_msg.get().append(self.gotThreadMsg) + if not pathToDatabase.isRunning and initDir: + pathToDatabase.Start(initDir) + + def gotThreadMsg(self, msg): + msg = pathToDatabase.Message.pop() + self["output"].setText(msg[1]) + if msg[0] == THREAD_FINISHED: + self["key_red"].setText("") + + def green(self): + self.close() + + def cancel(self): + if pathToDatabase.isRunning: + pathToDatabase.Cancel() + + def __onClose(self): + pathToDatabase.MessagePump.recv_msg.get().remove(self.gotThreadMsg) + + +class myHTTPClientFactory(HTTPClientFactory): + def __init__(self, url, method='GET', postdata=None, headers=None, + agent="SHOUTcast", timeout=0, cookies=None, + followRedirect=1, lastModified=None, etag=None): + HTTPClientFactory.__init__(self, url, method=method, postdata=postdata, + headers=headers, agent=agent, timeout=timeout, cookies=cookies,followRedirect=followRedirect) + +def sendUrlCommand(url, contextFactory=None, timeout=60, *args, **kwargs): + scheme, host, port, path = client._parse(url) + factory = myHTTPClientFactory(url, *args, **kwargs) + reactor.connectTCP(host, port, factory, timeout=timeout) + return factory.deferred + + +class MethodArguments: + def __init__(self, method = None, arguments = None): + self.method = method + self.arguments = arguments + +class CacheList: + def __init__(self, cache = True, index = 0, listview = [], headertext = "", methodarguments = None): + self.cache = cache + self.index = index + self.listview = listview + self.headertext = headertext + self.methodarguments = methodarguments + +class Item: + def __init__(self, text = "", mode = 0, id = -1, navigator = False, artistID = 0, albumID = 0, title = "", artist = "", filename = "", bitrate = None, length = "", genre = "", track = "", date = "", album = "", playlistID = 0, genreID = 0, songID = 0, join = True, PTS = None): + self.text = text + self.mode = mode + self.navigator = navigator + self.artistID = artistID + self.albumID = albumID + self.title = title + self.artist = artist + self.filename = filename + if bitrate is not None: + if join: + self.bitrate = "%d Kbps" % bitrate + else: + self.bitrate = bitrate + else: + self.bitrate = "" + self.length = length + self.genre = genre + if track is not None: + self.track = "Track %s" % track + else: + self.track = "" + if date is not None: + if join: + self.date = " (%s)" % date + else: + self.date = date + else: + self.date = "" + self.album = album + self.playlistID = playlistID + self.genreID = genreID + self.songID = songID + self.PTS = PTS + + +def OpenDatabase(): + connectstring = os_path.join(config.plugins.merlinmusicplayer.databasepath.value ,"iDream.db") + db_exists = False + if os_path.exists(connectstring): + db_exists = True + connection = sqlite.connect(connectstring) + if not db_exists : + connection.execute('CREATE TABLE IF NOT EXISTS Songs (song_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, filename TEXT NOT NULL UNIQUE, title TEXT, artist_id INTEGER, album_id INTEGER, genre_id INTEGER, tracknumber INTEGER, bitrate INTEGER, length TEXT, track TEXT, date TEXT, lyrics TEXT);') + connection.execute('CREATE TABLE IF NOT EXISTS Artists (artist_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, artist TEXT NOT NULL UNIQUE);') + connection.execute('CREATE TABLE IF NOT EXISTS Album (album_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, album_text TEXT NOT NULL UNIQUE);') + connection.execute('CREATE TABLE IF NOT EXISTS Genre (genre_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, genre_text TEXT NOT NULL UNIQUE);') + connection.execute('CREATE TABLE IF NOT EXISTS Playlists (playlist_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, playlist_text TEXT NOT NULL);') + connection.execute('CREATE TABLE IF NOT EXISTS Playlist_Songs (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, playlist_id INTEGER NOT NULL, song_id INTEGER NOT NULL);') + connection.execute('CREATE TABLE IF NOT EXISTS CurrentSongList (ID INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, song_id INTEGER, filename TEXT NOT NULL, title TEXT, artist TEXT, album TEXT, genre TEXT, bitrate TEXT, length TEXT, track TEXT, date TEXT, PTS INTEGER);') + return connection + +def getID3Tags(root,filename): + audio = None + isFlac = False + isAudio = True + title = "" + genre = "" + artist = "" + album = "" + tracknr = -1 + track = None + date = None + length = "" + bitrate = None + if filename.lower().endswith(".mp3"): + try: audio = MP3(os_path.join(root,filename), ID3 = EasyID3) + except: audio = None + elif filename.lower().endswith(".flac"): + try: + audio = FLAC(os_path.join(root,filename)) + isFlac = True + except: audio = None + elif filename.lower().endswith(".m4a"): + try: audio = EasyMP4(os_path.join(root,filename)) + except: audio = None + elif filename.lower().endswith(".ogg"): + try: audio = OggVorbis(os_path.join(root,filename)) + except: audio = None + else: + isAudio = False + if audio: + title = audio.get('title', [filename])[0] + genre = audio.get('genre', ['n/a'])[0] + artist = audio.get('artist', ['n/a'])[0] + album = audio.get('album', ['n/a'])[0] + tracknr = int(audio.get('tracknumber', ['-1'])[0].split("/")[0]) + track = audio.get('tracknumber', [None])[0] + date = audio.get('date', [None])[0] + + length = str(datetime.timedelta(seconds=int(audio.info.length))) + if not isFlac: + bitrate = audio.info.bitrate / 1000 + else: + bitrate = None + else: + if isAudio: + title = os_path.splitext(os_path.basename(filename))[0] + genre = "n/a" + artist = "n/a" + album = "n/a" + tracknr = -1 + track = None + date = None + length = "" + bitrate = None + + return audio, isAudio, title.encode("utf-8", 'ignore'), genre.encode("utf-8", 'ignore'),artist.encode("utf-8", 'ignore'),album.encode("utf-8", 'ignore'),tracknr,track,date,length.encode("utf-8", 'ignore'),bitrate + +class MerlinMusicPlayerScreenSaver(Screen): + + sz_w = getDesktop(0).size().width() + if sz_w == 1280: + skin = """ + + + + """ + elif sz_w == 1024: + skin = """ + + + + """ + + else: + skin = """ + + + + """ + + + def __init__(self, session): + self.session = session + Screen.__init__(self, session) + self["actions"] = ActionMap(["WizardActions", "DirectionActions", "ColorActions", "EventViewActions"], + { + "back": self.close, + "right": self.close, + "left": self.close, + "up": self.close, + "down": self.close, + "ok": self.close, + "pageUp": self.close, + "pageDown": self.close, + "yellow": self.close, + "blue": self.close, + "red": self.close, + "green": self.close, + "right": self.close, + "left": self.close, + "prevBouquet": self.close, + "nextBouquet": self.close, + "info": self.close, + + }, -1) + self["coverArt"] = MerlinMediaPixmap() + self.coverMoveTimer = eTimer() + self.coverMoveTimer.timeout.get().append(self.moveCoverArt) + self.coverMoveTimer.start(1) + self["display"] = Label() + + def updateDisplayText(self, text): + self["display"].setText(text) + + def updateLCD(self, text, line): + self.summaries.setText(text,line) + + def updateCover(self, filename = None, modus = 0): + print "[MerlinMusicPlayerScreenSaver] updating coverart with filename = %s and modus = %d" % (filename, modus) + if modus == 0: + if filename: + self["coverArt"].showCoverFromFile(filename) + else: + self["coverArt"].showDefaultCover() + elif modus == 1: + self["coverArt"].showDefaultCover() + elif modus == 2: + self["coverArt"].embeddedCoverArt() + elif modus == 3: + self["coverArt"].updateCoverArt(filename) + elif modus == 4: + self["coverArt"].showCoverFromFile(filename) + + def moveCoverArt(self): + x = randrange(getDesktop(0).size().width()-238) + y = randrange(getDesktop(0).size().height()-238-28) + self["coverArt"].move(ePoint(x,y)) + self["display"].move(ePoint(x,y+240)) + self.coverMoveTimer.start(15000) + + def createSummary(self): + return MerlinMusicPlayerLCDScreen + +class MerlinMusicPlayerScreen(Screen, InfoBarBase, InfoBarSeek, InfoBarNotifications): + + sz_w = getDesktop(0).size().width() + if sz_w == 1280: + skin = """ + + + + + + + + + + + + + + + Length,ShowHours + + + Position,ShowHours + + + + + """ + elif sz_w == 1024: + skin = """ + + + + + + + + + + + + + + + Length,ShowHours + + + Position,ShowHours + + + + + """ + else: + skin = """ + + + + + + + + + + + + + + + Length,ShowHours + + + Position,ShowHours + + + + + """ + + + def __init__(self, session, songlist, index, idreammode): + self.session = session + Screen.__init__(self, session) + InfoBarNotifications.__init__(self) + InfoBarBase.__init__(self) + self["actions"] = ActionMap(["WizardActions", "MediaPlayerActions", "EPGSelectActions", "MediaPlayerSeekActions", "ColorActions"], + { + "back": self.closePlayer, + "pause": self.pauseEntry, + "stop": self.stopEntry, + "right": self.playNext, + "left": self.playPrevious, + "up": self.showPlaylist, + "down" : self.showPlaylist, + "prevBouquet": self.shuffleList, + "nextBouquet": self.repeatSong, + "info" : self.showLyrics, + "yellow": self.pauseEntry, + "green": self.play, + "input_date_time": self.config, + }, -1) + + self.onClose.append(self.__onClose) + self.session.nav.stopService() + self["PositionGauge"] = ServicePositionGauge(self.session.nav) + self["coverArt"] = MerlinMediaPixmap() + self["repeat"] = MultiPixmap() + self["shuffle"] = MultiPixmap() + self["dvrStatus"] = MultiPixmap() + self["title"] = Label() + self["album"] = Label() + self["artist"] = Label() + self["genre"] = Label() + self["nextTitle"] = Label() + self.__event_tracker = ServiceEventTracker(screen=self, eventmap= + { + iPlayableService.evUpdatedInfo: self.__evUpdatedInfo, + iPlayableService.evUser+10: self.__evAudioDecodeError, + iPlayableService.evUser+12: self.__evPluginError, + iPlayableService.evUser+13: self.embeddedCoverArt, + iPlayableService.evStart: self.__serviceStarted, + }) + + InfoBarSeek.__init__(self, actionmap = "MediaPlayerSeekActions") + self.songList = songlist + self.origSongList = songlist[:] + self.currentIndex = index + self.shuffle = False + self.repeat = False + self.currentFilename = "" + self.currentGoogleCoverFile = "" + self.googleDownloadDir = os_path.join(config.plugins.merlinmusicplayer.googleimagepath.value, "downloaded_covers/" ) + if not os_path.exists(self.googleDownloadDir): + os_mkdir(self.googleDownloadDir) + self.init = 0 + self.onShown.append(self.__onShown) + # for lcd + self.currentTitle = "" + self.nextTitle = "" + self.screenSaverTimer = eTimer() + self.screenSaverTimer.timeout.get().append(self.screenSaverTimerTimeout) + self.screenSaverScreen = None + + self.iDreamMode = idreammode + + def embeddedCoverArt(self): + self["coverArt"].embeddedCoverArt() + if self.screenSaverScreen: + self.screenSaverScreen.updateCover(modus = 2) + + def screenSaverTimerTimeout(self): + if config.plugins.merlinmusicplayer.usescreensaver.value: + if self.screenSaverTimer.isActive(): + self.screenSaverTimer.stop() + if not self.screenSaverScreen: + self.screenSaverScreen = self.session.instantiateDialog(MerlinMusicPlayerScreenSaver) + self.session.execDialog(self.screenSaverScreen) + self.screenSaverScreen.updateLCD(self.currentTitle,1) + self.screenSaverScreen.updateLCD(self.nextTitle,4) + album = self["album"].getText() + if album: + text = "%s - %s" % (self["title"].getText(), album) + else: + text = self["title"].getText() + self.screenSaverScreen.updateDisplayText(text) + self.screenSaverScreen.updateCover(self["coverArt"].coverArtFileName, modus = 0) + + def resetScreenSaverTimer(self): + if config.plugins.merlinmusicplayer.usescreensaver.value and config.plugins.merlinmusicplayer.screensaverwait.value != 0: + if self.screenSaverTimer.isActive(): + self.screenSaverTimer.stop() + self.screenSaverTimer.start(config.plugins.merlinmusicplayer.screensaverwait.value * 60000) + + def __onShown(self): + if self.init == 0: + self.init = 1 + self["coverArt"].onShow() + self.playSong(self.songList[self.currentIndex][0].filename) + else: + self.summaries.setText(self.currentTitle,1) + self.summaries.setText(self.nextTitle,4) + if self.screenSaverScreen: + self.screenSaverScreen.doClose() + self.screenSaverScreen = None + self.resetScreenSaverTimer() + + def __onClose(self): + del self["coverArt"].picload + self.seek = None + + def config(self): + if self.screenSaverTimer.isActive(): + self.screenSaverTimer.stop() + self.session.openWithCallback(self.setupFinished, MerlinMusicPlayerSetup, False) + + def setupFinished(self, result): + if result: + self.googleDownloadDir = os_path.join(config.plugins.merlinmusicplayer.googleimagepath.value, "downloaded_covers/" ) + if not os_path.exists(self.googleDownloadDir): + os_mkdir(self.googleDownloadDir) + self.resetScreenSaverTimer() + + def closePlayer(self): + if config.plugins.merlinmusicplayer.startlastsonglist.value: + config.plugins.merlinmusicplayer.lastsonglistindex.value = self.currentIndex + config.plugins.merlinmusicplayer.lastsonglistindex.save() + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + cursor.execute("Delete from CurrentSongList;") + for song in self.origSongList: + cursor.execute("INSERT INTO CurrentSongList (song_id, filename,title,artist,album,genre, bitrate, length, track, date, PTS) VALUES(?,?,?,?,?,?,?,?,?,?,?);" , (song[0].songID, song[0].filename,song[0].title,song[0].artist,song[0].album,song[0].genre, song[0].bitrate, song[0].length, song[0].track, song[0].date, song[0].PTS)) + connection.commit() + cursor.close() + connection.close() + if self.screenSaverTimer.isActive(): + self.screenSaverTimer.stop() + self.close() + + def playSong(self, filename): + self.session.nav.stopService() + self.seek = None + self.currentFilename = filename + if not config.plugins.merlinmusicplayer.hardwaredecoder.value and self.currentFilename.lower().endswith(".mp3") and self.songList[self.currentIndex][0].PTS is None: + sref = eServiceReference(ENIGMA_MERLINPLAYER_ID, 0, self.currentFilename) # play mp3 file with merlinmp3player lib + self.session.nav.playService(sref) + if self.iDreamMode: + self.updateMusicInformation( self.songList[self.currentIndex][0].artist, self.songList[self.currentIndex][0].title, + self.songList[self.currentIndex][0].album, self.songList[self.currentIndex][0].genre, self.songList[self.currentIndex][0].date, clear = True ) + else: + path,filename = os_path.split(self.currentFilename) + audio, isAudio, title, genre,artist,album,tracknr,track,date,length,bitrate = getID3Tags(path,filename) + if audio: + if date: + year = "(%s)" % str(date) + else: + year = "" + self.updateMusicInformation( artist, title, album, genre, year, clear = True ) + else: + self.updateMusicInformation( title = title, clear = True) + audio = None + else: + sref = eServiceReference(4097, 0, self.currentFilename) + self.session.nav.playService(sref) + if self.songList[self.currentIndex][0].PTS is not None: + service = self.session.nav.getCurrentService() + if service: + self.seek = service.seek() + self.updateMusicInformationCUE() + self.ptsTimer = eTimer() + self.ptsTimer.callback.append(self.ptsTimerCallback) + self.ptsTimer.start(1000) + self["nextTitle"].setText(self.getNextTitle()) + + def ptsTimerCallback(self): + if self.seek: + pts = self.seek.getPlayPosition() + index = 0 + currentIndex = 0 + for songs in self.songList: + if pts[1] > songs[0].PTS: + currentIndex = index + else: + break + index +=1 + if currentIndex != self.currentIndex: + self.currentIndex = currentIndex + self.updateMusicInformationCUE() + self.ptsTimer.start(1000) + + def updateMusicInformationCUE(self): + self.updateSingleMusicInformation("artist", self.songList[self.currentIndex][0].artist, True) + self.updateSingleMusicInformation("title", self.songList[self.currentIndex][0].title, True) + self.updateSingleMusicInformation("album", self.songList[self.currentIndex][0].album, True) + self.summaries.setText(self.songList[self.currentIndex][0].title,1) + if self.screenSaverScreen: + self.screenSaverScreen.updateLCD(self.songList[self.currentIndex][0].title,1) + if self.songList[self.currentIndex][0].album: + self.screenSaverScreen.updateDisplayText("%s - %s" % (self.songList[self.currentIndex][0].title,self.songList[self.currentIndex][0].album)) + else: + self.screenSaverScreen.updateDisplayText(self.songList[self.currentIndex][0].title) + self.updateCover(self.songList[self.currentIndex][0].artist, self.songList[self.currentIndex][0].album) + self.currentTitle = self.songList[self.currentIndex][0].title + self["nextTitle"].setText(self.getNextTitle()) + + def __serviceStarted(self): + self["dvrStatus"].setPixmapNum(0) + + def __evUpdatedInfo(self): + currPlay = self.session.nav.getCurrentService() + if currPlay is not None: + sTitle = currPlay.info().getInfoString(iServiceInformation.sTagTitle) + sAlbum = currPlay.info().getInfoString(iServiceInformation.sTagAlbum) + sArtist = currPlay.info().getInfoString(iServiceInformation.sTagArtist) + sGenre = currPlay.info().getInfoString(iServiceInformation.sTagGenre) + sYear = currPlay.info().getInfoString(iServiceInformation.sTagDate) + if sYear: + sYear = "(%s)" % sYear + if not sTitle: + sTitle = os_path.splitext(os_path.basename(self.currentFilename))[0] + + if self.songList[self.currentIndex][0].PTS is None: + self.updateMusicInformation( sArtist, sTitle, sAlbum, sGenre, sYear, clear = True ) + else: + self.updateSingleMusicInformation("genre", sGenre, True) + else: + self.updateMusicInformation() + + def updateMusicInformation(self, artist = "", title = "", album = "", genre = "", year = "", clear = False): + if year and album: + album = "%s %s" % (album, year) + self.updateSingleMusicInformation("artist", artist, clear) + self.updateSingleMusicInformation("title", title, clear) + self.updateSingleMusicInformation("album", album, clear) + self.updateSingleMusicInformation("genre", genre, clear) + self.currentTitle = title + if not self.iDreamMode and self.songList[self.currentIndex][0].PTS is None: + # for lyrics + self.songList[self.currentIndex][0].title = title + self.songList[self.currentIndex][0].artist = artist + self.summaries.setText(title,1) + if self.screenSaverScreen: + self.screenSaverScreen.updateLCD(title,1) + if album: + self.screenSaverScreen.updateDisplayText("%s - %s" % (title,album)) + else: + self.screenSaverScreen.updateDisplayText(title) + self.updateCover(artist, album) + + def updateCover(self, artist, album): + hasCover = False + audio = None + audiotype = 0 + if self.currentFilename.lower().endswith(".mp3"): + try: + audio = ID3(self.currentFilename) + audiotype = 1 + except: audio = None + elif self.currentFilename.lower().endswith(".flac"): + try: + audio = FLAC(self.currentFilename) + audiotype = 2 + except: audio = None + elif self.currentFilename.lower().endswith(".m4a"): + try: + audio = MP4(self.currentFilename) + audiotype = 3 + except: audio = None + elif self.currentFilename.lower().endswith(".ogg"): + try: + audio = OggVorbis(self.currentFilename) + audiotype = 4 + except: audio = None + if audio: + if audiotype == 1: + apicframes = audio.getall("APIC") + if len(apicframes) >= 1: + hasCover = True + if not config.plugins.merlinmusicplayer.hardwaredecoder.value: + coverArtFile = file("/tmp/.id3coverart", 'wb') + coverArtFile.write(apicframes[0].data) + coverArtFile.close() + self["coverArt"].embeddedCoverArt() + if self.screenSaverScreen: + self.screenSaverScreen.updateCover(modus = 2) + elif audiotype == 2: + if len(audio.pictures) >= 1: + hasCover = True + elif audiotype == 3: + if 'covr' in audio.tags: + hasCover = True + elif audiotype == 4: + if 'METADATA_BLOCK_PICTURE' in audio.tags: + hasCover = True + audio = None + if not hasCover: + if not self["coverArt"].updateCoverArt(self.currentFilename): + if config.plugins.merlinmusicplayer.usegoogleimage.value: + self.getGoogleCover(artist, album) + else: + self["coverArt"].showDefaultCover() + if self.screenSaverScreen: + self.screenSaverScreen.updateCover(modus = 1) + else: + if self.screenSaverScreen: + self.screenSaverScreen.updateCover(filename = self.currentFilename, modus = 3) + self.currentGoogleCoverFile = "" + else: + self.currentGoogleCoverFile = "" + + def updateSingleMusicInformation(self, name, info, clear): + if info != "" or clear: + if self[name].getText() != info: + self[name].setText(info) + + def getGoogleCover(self, artist,album): + if artist != "" and album != "": + url = "http://images.google.de/images?q=%s+%s&btnG=Bilder-Suche" % (quote(album),quote(artist)) + sendUrlCommand(url, None,10).addCallback(self.googleImageCallback).addErrback(self.coverDownloadFailed) + else: + self["coverArt"].showDefaultCover() + + def googleImageCallback(self, result): + foundPos = result.find("imgres?imgurl=") + foundPos2 = result.find("&imgrefurl=") + if foundPos != -1 and foundPos2 != -1: + url = result[foundPos+14:foundPos2] + parts = url.split("/") + filename = parts[-1] + if filename != self.currentGoogleCoverFile: + self.currentGoogleCoverFile = filename + filename = self.googleDownloadDir + parts[-1] + if os_path.exists(filename): + print "[MerlinMusicPlayer] using cover from %s " % filename + self["coverArt"].showCoverFromFile(filename) + if self.screenSaverScreen: + self.screenSaverScreen.updateCover(filename = filename, modus = 4) + else: + print "[MerlinMusicPlayer] downloading cover from %s " % url + downloadPage(url , self.googleDownloadDir + parts[-1]).addCallback(boundFunction(self.coverDownloadFinished, filename)).addErrback(self.coverDownloadFailed) + + def coverDownloadFailed(self,result): + print "[MerlinMusicPlayer] cover download failed: %s " % result + self["coverArt"].showDefaultCover() + if self.screenSaverScreen: + self.screenSaverScreen.updateCover(modus = 1) + + def coverDownloadFinished(self,filename, result): + print "[MerlinMusicPlayer] cover download finished" + self["coverArt"].showCoverFromFile(filename) + if self.screenSaverScreen: + self.screenSaverScreen.updateCover(filename = filename, modus = 4) + + def __evAudioDecodeError(self): + currPlay = self.session.nav.getCurrentService() + sAudioType = currPlay.info().getInfoString(iServiceInformation.sUser+10) + print "[MerlinMusicPlayer] audio-codec %s can't be decoded by hardware" % (sAudioType) + self.session.open(MessageBox, _("This Dreambox can't decode %s streams!") % sAudioType, type = MessageBox.TYPE_INFO,timeout = 20 ) + + def __evPluginError(self): + currPlay = self.session.nav.getCurrentService() + message = currPlay.info().getInfoString(iServiceInformation.sUser+12) + print "[MerlinMusicPlayer]" , message + self.session.open(MessageBox, message, type = MessageBox.TYPE_INFO,timeout = 20 ) + + def doEofInternal(self, playing): + if playing: + self.playNext() + + def checkSkipShowHideLock(self): + self.updatedSeekState() + + def updatedSeekState(self): + if self.seekstate == self.SEEK_STATE_PAUSE: + self["dvrStatus"].setPixmapNum(1) + elif self.seekstate == self.SEEK_STATE_PLAY: + self["dvrStatus"].setPixmapNum(0) + + def pauseEntry(self): + self.pauseService() + self.resetScreenSaverTimer() + + def play(self): + #play the current song from beginning again + if self.songList[self.currentIndex][0].PTS is None: + self.playSong(self.songList[self.currentIndex][0].filename) + else: + if self.seek: + self.seek.seekTo(self.songList[self.currentIndex][0].PTS) + self.updatedSeekState() + self.resetScreenSaverTimer() + + def unPauseService(self): + self.setSeekState(self.SEEK_STATE_PLAY) + + def stopEntry(self): + self.seek = None + self.session.nav.stopService() + self.origSongList = [] + self.songList = [] + if config.plugins.merlinmusicplayer.startlastsonglist.value: + config.plugins.merlinmusicplayer.lastsonglistindex.value = -1 + config.plugins.merlinmusicplayer.lastsonglistindex.save() + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + cursor.execute("Delete from CurrentSongList;") + connection.commit() + cursor.close() + connection.close() + self.resetScreenSaverTimer() + self.close() + + def playNext(self): + if not self.repeat: + if self.currentIndex +1 > len(self.songList) -1: + self.currentIndex = 0 + else: + self.currentIndex += 1 + if self.songList[self.currentIndex][0].PTS is None: + self.playSong(self.songList[self.currentIndex][0].filename) + else: + self.playCUETrack() + if not self.screenSaverScreen: + self.resetScreenSaverTimer() + + def playPrevious(self): + if not self.repeat: + if self.currentIndex - 1 < 0: + self.currentIndex = len(self.songList) - 1 + else: + self.currentIndex -= 1 + + if self.songList[self.currentIndex][0].PTS is None: + self.playSong(self.songList[self.currentIndex][0].filename) + else: + self.playCUETrack() + self.resetScreenSaverTimer() + + def getNextTitle(self): + if self.repeat: + index = self.currentIndex + else: + if self.currentIndex + 1 > len(self.songList) -1: + index = 0 + else: + index = self.currentIndex + 1 + if self.iDreamMode or self.songList[index][0].PTS is not None: + text = "%s - %s" % (self.songList[index][0].title, self.songList[index][0].artist) + else: + if self.songList[index][0].filename.lower().startswith("http://"): + text = self.songList[index][0].filename + else: + path,filename = os_path.split(self.songList[index][0].filename) + audio, isAudio, title, genre,artist,album,tracknr,track,date,length,bitrate = getID3Tags(path,filename) + if audio: + if artist: + text = "%s - %s" % (title, artist) + else: + text = title + else: + text = title + audio = None + self.nextTitle = text + self.summaries.setText(text,4) + if self.screenSaverScreen: + self.screenSaverScreen.updateLCD(text,4) + return str(text) + + def shuffleList(self): + if self.songList[self.currentIndex][0].PTS is None: # not implemented for cue files yet + self.shuffle = not self.shuffle + if self.shuffle: + self["shuffle"].setPixmapNum(1) + shuffle(self.songList) + else: + self.songList = self.origSongList[:] + self["shuffle"].setPixmapNum(0) + index = 0 + for x in self.songList: + if x[0].filename == self.currentFilename: + self.currentIndex = index + break + index += 1 + self["nextTitle"].setText(self.getNextTitle()) + else: + self.session.open(MessageBox, _("Shuffle is not available yet with cue-files!"), type = MessageBox.TYPE_INFO,timeout = 20 ) + self.resetScreenSaverTimer() + + def repeatSong(self): + if self.songList[self.currentIndex][0].PTS is None: # not implemented for cue files yet + self.repeat = not self.repeat + if self.repeat: + self["repeat"].setPixmapNum(1) + else: + self["repeat"].setPixmapNum(0) + self["nextTitle"].setText(self.getNextTitle()) + else: + self.session.open(MessageBox, _("Repeat is not available yet with cue-files!"), type = MessageBox.TYPE_INFO,timeout = 20 ) + self.resetScreenSaverTimer() + + def showPlaylist(self): + if self.screenSaverTimer.isActive(): + self.screenSaverTimer.stop() + self.session.openWithCallback(self.showPlaylistCallback, MerlinMusicPlayerSongList, self.songList, self.currentIndex, self.iDreamMode) + + def showPlaylistCallback(self, index): + if index != -1: + self.currentIndex = index + + if self.songList[self.currentIndex][0].PTS is None: + self.playSong(self.songList[self.currentIndex][0].filename) + else: + self.playCUETrack() + + self.resetScreenSaverTimer() + + def playCUETrack(self): + if self.ptsTimer.isActive(): + self.ptsTimer.stop() + if self.seek: + self.seek.seekTo(self.songList[self.currentIndex][0].PTS) + self.updatedSeekState() + self.updateMusicInformationCUE() + self.ptsTimer.start(1000) + + def showLyrics(self): + if self.screenSaverTimer.isActive(): + self.screenSaverTimer.stop() + self.session.openWithCallback(self.resetScreenSaverTimer, MerlinMusicPlayerLyrics, self.songList[self.currentIndex][0]) + + def createSummary(self): + return MerlinMusicPlayerLCDScreen + +class MerlinMusicPlayerLyrics(Screen): + + sz_w = getDesktop(0).size().width() + if sz_w == 1280: + skin = """ + + + + + + + + + """ + elif sz_w == 1024: + skin = """ + + + + + + + + """ + else: + skin = """ + + + + + + + + """ + + + def __init__(self, session, currentsong): + self.session = session + Screen.__init__(self, session) + self["headertext"] = Label(_("Merlin Music Player Lyrics")) + self["resulttext"] = Label(_("Getting lyrics from api.leoslyrics.com...")) + self["actions"] = ActionMap(["WizardActions", "DirectionActions"], + { + "back": self.close, + "upUp": self.pageUp, + "leftUp": self.pageUp, + "downUp": self.pageDown, + "rightUp": self.pageDown, + }, -1) + self["lyric_text"] = ScrollLabel() + self.currentSong = currentsong + self.onLayoutFinish.append(self.startRun) + + def startRun(self): + url = "http://api.leoslyrics.com/api_search.php?auth=duane&artist=%s&songtitle=%s" % (quote(self.currentSong.artist), quote(self.currentSong.title)) + sendUrlCommand(url, None,10).addCallback(self.getHID).addErrback(self.urlError) + + def urlError(self, error = None): + if error is not None: + self["resulttext"].setText(str(error.getErrorMessage())) + + def getHID(self, xmlstring): + root = cet_fromstring(xmlstring) + url = "" + child = root.find("searchResults") + if child: + child2 = child.find("result") + if child2: + url = "http://api.leoslyrics.com/api_lyrics.php?auth=duane&hid=%s" % quote(child2.get("hid")) + sendUrlCommand(url, None,10).addCallback(self.getLyrics).addErrback(self.urlError) + if not url: + self["resulttext"].setText(_("No lyrics found")) + + def getLyrics(self, xmlstring): + root = cet_fromstring(xmlstring) + lyrictext = "" + child = root.find("lyric") + if child: + title = child.findtext("title").encode("utf-8", 'ignore') + child2 = child.find("artist") + if child2: + artist = child2.findtext("name").encode("utf-8", 'ignore') + else: + artist = "" + lyrictext = child.findtext("text").encode("utf-8", 'ignore') + self["lyric_text"].setText(lyrictext) + result = _("Response -> lyrics for: %s (%s)") % (title,artist) + self["resulttext"].setText(result) + if not lyrictext: + self["resulttext"].setText(_("No lyrics found")) + + def pageUp(self): + self["lyric_text"].pageUp() + + def pageDown(self): + self["lyric_text"].pageDown() + +class MerlinMusicPlayerSongList(Screen): + + sz_w = getDesktop(0).size().width() + if sz_w == 1280: + skin = """ + + + + + + + + """ + elif sz_w == 1024: + skin = """ + + + + + + + """ + else: + skin = """ + + + + + + + """ + + + def __init__(self, session, songlist, index, idreammode): + self.session = session + Screen.__init__(self, session) + self["headertext"] = Label(_("Merlin Music Player Songlist")) + self["list"] = iDreamList() + self["list"].connectSelChanged(self.lcdUpdate) + self["actions"] = ActionMap(["WizardActions"], + { + "ok": self.ok, + "back": self.closing, + }, -1) + self.songList = songlist + self.index = index + self.iDreamMode = idreammode + self.onLayoutFinish.append(self.startRun) + self.onShown.append(self.lcdUpdate) + + def startRun(self): + if self.iDreamMode: + self["list"].setMode(10) # songlist + self["list"].setList(self.songList) + self["list"].moveToIndex(self.index) + + def ok(self): + self.close(self["list"].getCurrentIndex()) + + def closing(self): + self.close(-1) + + def lcdUpdate(self): + try: + index = self["list"].getCurrentIndex() + songlist = self["list"].getList() + self.summaries.setText(songlist[index][0].title,1) + count = self["list"].getItemCount() + # voheriges + index -= 1 + if index < 0: + index = count + self.summaries.setText(songlist[index][0].title,3) + # naechstes + index = self["list"].getCurrentIndex() + 1 + if index > count: + index = 0 + self.summaries.setText(songlist[index][0].title,4) + except: pass + + def createSummary(self): + return MerlinMusicPlayerLCDScreenText + +class iDreamMerlin(Screen): + + + sz_w = getDesktop(0).size().width() + if sz_w == 1280: + skin = """ + + + + + + + + + + + + + """ + elif sz_w == 1024: + skin = """ + + + + + + + + + + + + """ + else: + skin = """ + + + + + + + + + + + + """ + + + def __init__(self, session): + self.session = session + Screen.__init__(self, session) + self["list"] = iDreamList() + self["list"].connectSelChanged(self.lcdUpdate) + + + self["actions"] = ActionMap(["WizardActions", "DirectionActions", "ColorActions", "EPGSelectActions"], + { + "ok": self.ok, + "back": self.closing, + "red": self.red_pressed, + "green": self.green_pressed, + "yellow": self.yellow_pressed, + "blue": self.blue_pressed, + "input_date_time": self.menu_pressed, + "info" : self.info_pressed, + }, -1) + + self["actions2"] = NumberActionMap(["InputActions"], + { + "0": self.keyNumber_pressed, + }, -1) + + self.onLayoutFinish.append(self.startRun) + self.onShown.append(self.lcdUpdate) + self.onClose.append(self.__onClose) + + self.CurrentService = self.session.nav.getCurrentlyPlayingServiceReference() + self.session.nav.stopService() + + self.mode = 0 + self.mainMenuList = [] + self.cacheList = [] + self.LastMethod = None + self.player = None + + self["key_red"] = StaticText("") + self["key_green"] = StaticText("") + self["key_yellow"] = StaticText("") + self["key_blue"] = StaticText("") + self["headertext"] = Label(_("iDream Main Menu")) + + def getPlayList(self): + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + playList = [] + cursor.execute("select playlist_id,playlist_text from playlists order by playlist_text;") + for row in cursor: + playList.append((row[1], row[0])) + cursor.close() + connection.close() + return playList + + def sqlCommand(self, sqlSatement): + connection = OpenDatabase() + cursor = connection.cursor() + cursor.execute(sqlSatement) + cursor.close() + connection.commit() + connection.close() + + def clearCache(self): + for items in self.cacheList: + items.cache = False + items.listview = [] + items.headertext = "" + + def getCurrentSelection(self): + sel = None + try: sel = self["list"].l.getCurrentSelection()[0] + except: pass + return sel + + def addListToPlaylistConfirmed(self, methodName, answer): + if answer: + playList = self.getPlayList() + if len(playList): + self.session.openWithCallback(methodName, ChoiceBox,list = playList) + else: + self.session.openWithCallback(self.createPlaylistConfirmed, MessageBox, _("There are no playlists defined.\nDo you want to create a new playlist?")) + + def menu_pressed(self): + options = [(_("Configuration"), self.config),(_("Search in iDream database"), self.searchInIDreamDatabase),] + options.extend(((_("Scan path for music files and add them to database"), self.scanDir),)) + if self.mode != 1: + options.extend(((_("Create new playlist"), self.createPlaylist),)) + if self["list"].getDisplaySongMode(): + if self.mode == 2: + options.extend(((_("Delete song from current playlist"), self.deleteSongFromPlaylist),)) + else: + options.extend(((_("Add selected song to a playlist"), self.addSongToPlaylist),)) + if self.mode == 18: + options.extend(((_("Add all songs from selected album to a playlist"), self.addAlbumToPlaylist),)) + elif self.mode == 19: + options.extend(((_("Add all songs from selected artist to a playlist"), self.addArtistToPlaylist),)) + options.extend(((_("Delete song from database"), self.deleteSongFromDatabase),)) + else: + if self.mode == 1: + options.extend(((_("Delete selected playlist"), self.deletePlaylist),)) + elif self.mode == 4: + options.extend(((_("Add all songs from selected artist to a playlist"), self.addArtistToPlaylist),)) + elif self.mode == 5 or self.mode == 7: + options.extend(((_("Add all songs from selected album to a playlist"), self.addAlbumToPlaylist),)) + elif self.mode == 13: + options.extend(((_("Add all songs from selected genre to a playlist"), self.addGenreToPlaylist),)) + self.session.openWithCallback(self.menuCallback, ChoiceBox,list = options) + + def menuCallback(self, ret): + ret and ret[1]() + + def scanDir(self): + SelectPath + self.session.openWithCallback(self.pathSelected,SelectPath,"/media/") + + def pathSelected(self, res): + if res is not None: + self.session.openWithCallback(self.filesAdded, iDreamAddToDatabase,res) + + def filesAdded(self): + if pathToDatabase.isRunning: + self.close() + else: + self.red_pressed() + + + + + def addGenreToPlaylist(self): + self.session.openWithCallback(boundFunction(self.addListToPlaylistConfirmed,self.addGenreToPlaylistConfirmedCallback), MessageBox, _("Do you really want to add all songs from that genre to a playlist?")) + + def addGenreToPlaylistConfirmedCallback(self, ret): + if ret: + sel = self.getCurrentSelection() + if sel: + self.sqlCommand("INSERT INTO Playlist_Songs (playlist_id,song_id) select %d, song_id from songs where genre_id=%d order by album_id,tracknumber,title,filename;" % (ret[1],sel.genreID)) + self.clearCache() + + def addArtistToPlaylist(self): + self.session.openWithCallback(boundFunction(self.addListToPlaylistConfirmed, self.addArtistToPlaylistConfirmedCallback), MessageBox, _("Do you really want to add all songs from that artist to a playlist?")) + + def addArtistToPlaylistConfirmedCallback(self, ret): + if ret: + sel = self.getCurrentSelection() + if sel: + self.sqlCommand("INSERT INTO Playlist_Songs (playlist_id,song_id) select %d, song_id from songs where artist_id=%d order by album_id,tracknumber,title,filename;" % (ret[1],sel.artistID)) + self.clearCache() + + def addAlbumToPlaylist(self): + self.session.openWithCallback(boundFunction(self.addListToPlaylistConfirmed, self.addAlbumToPlaylistConfirmedCallback), MessageBox, _("Do you really want to add all songs from that album to a playlist?")) + + def addAlbumToPlaylistConfirmedCallback(self, ret): + if ret: + sel = self.getCurrentSelection() + if sel: + self.sqlCommand("INSERT INTO Playlist_Songs (playlist_id,song_id) select %d, song_id from songs where album_id=%d order by tracknumber,title,filename;" % (ret[1],sel.albumID)) + self.clearCache() + + def deletePlaylist(self): + self.session.openWithCallback(self.deletePlaylistConfirmed, MessageBox, _("Do you really want to delete the current playlist?")) + + def deletePlaylistConfirmed(self, answer): + if answer: + sel = self.getCurrentSelection() + if sel: + self.sqlCommand("delete from playlist_songs where playlist_id = %d" % (sel.playlistID)) + self.sqlCommand("delete from playlists where playlist_id = %d" % (sel.playlistID)) + self["list"].removeItem(self["list"].getCurrentIndex()) + self.clearCache() + + + def deleteSongFromPlaylist(self): + self.session.openWithCallback(self.deleteSongFromPlaylistConfirmed, MessageBox, _("Do you really want to delete that song the current playlist?")) + + def deleteSongFromPlaylistConfirmed(self, answer): + if answer: + sel = self.getCurrentSelection() + if sel: + self.sqlCommand("delete from playlist_songs where song_id = %d" % (sel.songID)) + self["list"].removeItem(self["list"].getCurrentIndex()) + self.clearCache() + + def deleteSongFromDatabase(self): + self.session.openWithCallback(self.deleteSongFromDatabaseConfirmed, MessageBox, _("Do you really want to delete that song from the database?")) + + def deleteSongFromDatabaseConfirmed(self, answer): + if answer: + sel = self.getCurrentSelection() + if sel: + self.sqlCommand("delete from playlist_songs where song_id = %d" % (sel.songID)) + self.sqlCommand("delete from songs where song_id = %d" % (sel.songID)) + self["list"].removeItem(self["list"].getCurrentIndex()) + self.clearCache() + + def addSongToPlaylist(self): + playList = self.getPlayList() + if len(playList): + self.session.openWithCallback(self.addSongToPlaylistCallback, ChoiceBox,list = playList) + else: + self.session.openWithCallback(self.createPlaylistConfirmed, MessageBox, _("There are no playlists defined.\nDo you want to create a new playlist?")) + + def createPlaylistConfirmed(self, val): + if val: + self.createPlaylist() + + def addSongToPlaylistCallback(self,ret): + if ret: + sel = self.getCurrentSelection() + if sel: + self.sqlCommand("INSERT INTO Playlist_Songs (playlist_id,song_id) VALUES(%d,%d);" % (ret[1],sel.songID)) + self.clearCache() + + def createPlaylist(self): + self.session.openWithCallback(self.createPlaylistFinished, VirtualKeyBoard, title = _("Enter name for playlist")) + + def createPlaylistFinished(self, text = None): + if text: + self.sqlCommand("""INSERT INTO Playlists (playlist_text) VALUES("%s");""" % (text)) + self.clearCache() + self.menu_pressed() + + def searchInIDreamDatabase(self): + options = [(_("search for title"), 1), + (_("search for artist"), 2), + (_("search for album"), 3), + (_("search in all of them"), 4),] + self.session.openWithCallback(self.enterSearchText, ChoiceBox,list = options) + + def enterSearchText(self, ret): + if ret: + self.session.openWithCallback(boundFunction(self.enterSearchTextFinished,ret[1]), VirtualKeyBoard, title = _("Enter search-text")) + + def enterSearchTextFinished(self, searchType, searchText = None): + if searchText: + search = "%" + searchText + "%" + if searchType == 1: + sql_where = "where title like '%s'" % search + text = _("""Search results for "%s" in all titles""") % searchText + elif searchType == 2: + sql_where = "where artists.artist like '%s'" % search + text = _("""Search results for "%s" in all artists""") % searchText + elif searchType == 3: + sql_where = "where album_text like '%s'" % search + text = _("""Search results for "%s" in all albums""") % searchText + else: + sql_where = "where (title like '%s' or artists.artist like '%s' or album_text like '%s')" % (search,search,search) + text = _("""Search results for "%s" in title, artist or album""") % searchText + self.setButtons(red = True, yellow = True, blue = True) + oldmode = self.mode + self.mode = 20 + self["list"].setMode(self.mode) + self.buildSearchSongList(sql_where, text, oldmode, True) + + + def keyNumber_pressed(self, number): + if number == 0 and self.mode != 0: + self["list"].moveToIndex(0) + self.ok() + + def ok(self): + sel = self.getCurrentSelection() + if sel is None: + return + if sel.mode == 99: + self.green_pressed() + else: + self.mode = sel.mode + self["list"].setMode(self.mode) + if sel.navigator and len(self.cacheList) > 0: + cache = self.cacheList.pop() + else: + cache = CacheList(cache = False, index = -1) + if sel.navigator: + self["headertext"].setText(cache.headertext) + if cache.cache: + self["list"].setList(cache.listview) + self.LastMethod = MethodArguments(method = cache.methodarguments.method, arguments = cache.methodarguments.arguments) + else: + cache.methodarguments.method(**cache.methodarguments.arguments) + self["list"].moveToIndex(cache.index) + if self.mode == 0: + self.setButtons() + if not sel.navigator: + self.buildMainMenuList() + elif self.mode == 1: + self.setButtons(red = True) + if not sel.navigator: + self.buildPlaylistList(addToCache = True) + elif self.mode == 2: + self.setButtons(red = True, green = True, yellow = True, blue = True) + if not sel.navigator: + self.buildPlaylistSongList(playlistID = sel.playlistID, addToCache = True) + elif self.mode == 4: + self.setButtons(red = True) + if not sel.navigator: + self.buildArtistList(addToCache = True) + elif self.mode == 5: + self.setButtons(red = True) + if not sel.navigator: + self.buildArtistAlbumList(sel.artistID, addToCache = True) + elif self.mode == 6: + self.setButtons(red = True, green = True, yellow = True) + if not sel.navigator: + self.buildAlbumSongList(albumID = sel.albumID, mode = 5, addToCache = True) + elif self.mode == 7: + self.setButtons(red = True) + if not sel.navigator: + self.buildAlbumList(addToCache = True) + elif self.mode == 8: + self.setButtons(red = True, green = True, yellow = True) + if not sel.navigator: + self.buildAlbumSongList(albumID = sel.albumID, mode = 7, addToCache = True) + elif self.mode == 10: + self.setButtons(red = True, green = True, yellow = True, blue = True) + if not sel.navigator: + self.buildSongList(addToCache = True) + elif self.mode == 13: + self.setButtons(red = True) + if not sel.navigator: + self.buildGenreList(addToCache = True) + elif self.mode == 14: + self.setButtons(red = True, green = True, yellow = True, blue = True) + if not sel.navigator: + self.buildGenreSongList(genreID = sel.genreID, addToCache = True) + elif self.mode == 18 or self.mode == 19: + if self.mode == 18: + self.setButtons(red = True, green = True, yellow = True) + if self.mode == 19: + self.setButtons(red = True, green = True, blue = True) + if not sel.navigator: + self.red_pressed() # back to main menu --> normally that can not be happened + elif self.mode == 20: + self.setButtons(red = True, green = True, yellow = True, blue = True) + if not sel.navigator: + self.red_pressed() # back to main menu --> normally that can not be happened + + def buildPlaylistList(self, addToCache): + if addToCache: + self.cacheList.append(CacheList(index = self["list"].getCurrentIndex(), listview = self["list"].getList(), headertext = self["headertext"].getText(), methodarguments = self.LastMethod)) + arguments = {} + arguments["addToCache"] = False + self.LastMethod = MethodArguments(method = self.buildPlaylistList, arguments = arguments) + self["headertext"].setText(_("Playlists")) + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + playlistList = [] + playlistList.append((Item(text = _("[back]"), mode = 0, navigator = True),)) + cursor.execute("select playlists.playlist_id, playlist_text, count(Playlist_Songs.playlist_id) from playlists left outer join Playlist_Songs on playlists.playlist_id = Playlist_Songs.playlist_id group by playlists.playlist_id order by playlists.playlist_text;") + for row in cursor: + playlistList.append((Item(text = "%s (%d)" % (row[1], row[2]), mode = 2, playlistID = row[0]),)) + cursor.close() + connection.close() + self["list"].setList(playlistList) + if len(playlistList) > 1: + self["list"].moveToIndex(1) + + def buildPlaylistSongList(self, playlistID, addToCache): + if addToCache: + self.cacheList.append(CacheList(index = self["list"].getCurrentIndex(), listview = self["list"].getList(), headertext = self["headertext"].getText(), methodarguments = self.LastMethod)) + arguments = {} + arguments["playlistID"] = playlistID + arguments["addToCache"] = False + self.LastMethod = MethodArguments(method = self.buildPlaylistSongList, arguments = arguments) + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + playlistSongList = [] + playlistSongList.append((Item(text = _("[back]"), mode = 1, navigator = True),)) + cursor.execute("select songs.song_id, title, artists.artist, filename, songs.artist_id, bitrate, length, genre_text, track, date, album_text, songs.Album_id from songs inner join artists on songs.artist_id = artists.artist_id inner join Album on songs.Album_id = Album.Album_id inner join genre on songs.genre_id = genre.genre_id inner join playlist_songs on songs.song_id = playlist_songs.song_id where playlist_songs.playlist_id = %d order by playlist_songs.id;" % (playlistID)) + for row in cursor: + playlistSongList.append((Item(mode = 99, songID = row[0], title = row[1], artist = row[2], filename = row[3], artistID = row[4], bitrate = row[5], length = row[6], genre = row[7], track = row[8], date = row[9], album = row[10], albumID = row[11], playlistID = playlistID),)) + cursor.execute("SELECT playlist_text from playlists where playlist_id = %d;" % playlistID) + row = cursor.fetchone() + self["headertext"].setText(_("Playlist (%s) -> Song List") % row[0]) + cursor.close() + connection.close() + self["list"].setList(playlistSongList) + if len(playlistSongList) > 1: + self["list"].moveToIndex(1) + + def buildGenreList(self, addToCache): + if addToCache: + self.cacheList.append(CacheList(index = self["list"].getCurrentIndex(), listview = self["list"].getList(), headertext = self["headertext"].getText(), methodarguments = self.LastMethod)) + arguments = {} + arguments["addToCache"] = False + self.LastMethod = MethodArguments(method = self.buildGenreList, arguments = arguments) + self["headertext"].setText(_("Genre List")) + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + genreList = [] + genreList.append((Item(text = _("[back]"), mode = 0, navigator = True),)) + cursor.execute("select Genre.genre_id,Genre.Genre_text, count(*) from songs inner join Genre on songs.genre_id = Genre.Genre_id group by songs.Genre_id order by Genre.Genre_text;") + for row in cursor: + genreList.append((Item(text = "%s (%d)" % (row[1], row[2]), mode = 14, genreID = row[0]),)) + cursor.close() + connection.close() + self["list"].setList(genreList) + if len(genreList) > 1: + self["list"].moveToIndex(1) + + def buildGenreSongList(self, genreID, addToCache): + if addToCache: + self.cacheList.append(CacheList(index = self["list"].getCurrentIndex(), listview = self["list"].getList(), headertext = self["headertext"].getText(), methodarguments = self.LastMethod)) + arguments = {} + arguments["genreID"] = genreID + arguments["addToCache"] = False + self.LastMethod = MethodArguments(method = self.buildGenreSongList, arguments = arguments) + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + genreSongList = [] + genreSongList.append((Item(text = _("[back]"), mode = 13, navigator = True),)) + cursor.execute("select song_id, title, artists.artist, filename, songs.artist_id, bitrate, length, genre_text, track, date, album_text, songs.Album_id from songs inner join artists on songs.artist_id = artists.artist_id inner join Album on songs.Album_id = Album.Album_id inner join genre on songs.genre_id = genre.genre_id where songs.genre_id = %d order by title, filename;" % (genreID)) + for row in cursor: + genreSongList.append((Item(mode = 99, songID = row[0], title = row[1], artist = row[2], filename = row[3], artistID = row[4], bitrate = row[5], length = row[6], genre = row[7], track = row[8], date = row[9], album = row[10], albumID = row[11], genreID = genreID),)) + cursor.execute("SELECT genre_text from genre where genre_ID = %d;" % genreID) + row = cursor.fetchone() + self["headertext"].setText(_("Genre (%s) -> Song List") % row[0]) + cursor.close() + connection.close() + self["list"].setList(genreSongList) + if len(genreSongList) > 1: + self["list"].moveToIndex(1) + + def setButtons(self, red = False, green = False, yellow = False, blue = False): + if red: + self["key_red"].setText("Main Menu") + else: + self["key_red"].setText("") + if green: + self["key_green"].setText("Play") + else: + self["key_green"].setText("") + if yellow: + self["key_yellow"].setText("All Artists") + else: + self["key_yellow"].setText("") + if blue: + self["key_blue"].setText("Show Album") + else: + self["key_blue"].setText("") + + def info_pressed(self): + if self.player is not None: + if self.player.songList: + self.session.execDialog(self.player) + + def green_pressed(self): + sel = self["list"].l.getCurrentSelection()[0] + if sel is None: + return + if sel.songID != 0: + if self.player is not None: + self.player.doClose() + self.player = None + self.player = self.session.instantiateDialog(MerlinMusicPlayerScreen,self["list"].getList()[1:], self["list"].getCurrentIndex() -1, True) + self.session.execDialog(self.player) + + def red_pressed(self): + self.cacheList = [] + self.setButtons() + self.mode = 0 + self["list"].setMode(self.mode) + self.buildMainMenuList() + + def yellow_pressed(self): + sel = self["list"].l.getCurrentSelection()[0] + if sel.artistID != 0: + oldmode = self.mode + self.mode = 19 + self.setButtons(red = True, green = True, blue = True) + self["list"].setMode(self.mode) + self.buildArtistSongList(artistID = sel.artistID, mode = oldmode, addToCache = True) + + def blue_pressed(self): + sel = self["list"].l.getCurrentSelection()[0] + if sel.albumID != 0: + self.setButtons(red = True, green = True, yellow = True) + oldmode = self.mode + self.mode = 18 + self["list"].setMode(self.mode) + self.buildAlbumSongList(albumID = sel.albumID, mode = oldmode, addToCache = True) + + def buildSongList(self, addToCache): + if addToCache: + self.cacheList.append(CacheList(index = self["list"].getCurrentIndex(), listview = self["list"].getList(), headertext = self["headertext"].getText(), methodarguments = self.LastMethod)) + arguments = {} + arguments["addToCache"] = False + self.LastMethod = MethodArguments(method = self.buildSongList, arguments = arguments) + self["headertext"].setText(_("All Songs")) + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + SongList = [] + SongList.append((Item(text = _("[back]"), mode = 0, navigator = True),)) + cursor.execute("select song_id, title, artists.artist, filename, songs.artist_id, bitrate, length, genre_text, track, date, album_text, songs.Album_id from songs inner join artists on songs.artist_id = artists.artist_id inner join Album on songs.Album_id = Album.Album_id inner join genre on songs.genre_id = genre.genre_id order by title, filename;") + for row in cursor: + SongList.append((Item(mode = 99, songID = row[0], title = row[1], artist = row[2], filename = row[3], artistID = row[4], bitrate = row[5], length = row[6], genre = row[7], track = row[8], date = row[9], album = row[10], albumID = row[11]),)) + cursor.close() + connection.close() + self["list"].setList(SongList) + if len(SongList) > 1: + self["list"].moveToIndex(1) + + + def buildSearchSongList(self, sql_where, headerText, mode, addToCache): + if addToCache: + self.cacheList.append(CacheList(index = self["list"].getCurrentIndex(), listview = self["list"].getList(), headertext = self["headertext"].getText(), methodarguments = self.LastMethod)) + arguments = {} + arguments["sql_where"] = sql_where + arguments["headerText"] = headerText + arguments["mode"] = mode + arguments["addToCache"] = False + self.LastMethod = MethodArguments(method = self.buildSearchSongList, arguments = arguments) + self["headertext"].setText(headerText) + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + SongList = [] + SongList.append((Item(text = _("[back]"), mode = mode, navigator = True),)) + cursor.execute("select song_id, title, artists.artist, filename, songs.artist_id, bitrate, length, genre_text, track, date, album_text, songs.Album_id from songs inner join artists on songs.artist_id = artists.artist_id inner join Album on songs.Album_id = Album.Album_id inner join genre on songs.genre_id = genre.genre_id %s order by title, filename;" % sql_where) + for row in cursor: + SongList.append((Item(mode = 99, songID = row[0], title = row[1], artist = row[2], filename = row[3], artistID = row[4], bitrate = row[5], length = row[6], genre = row[7], track = row[8], date = row[9], album = row[10], albumID = row[11]),)) + cursor.close() + connection.close() + self["list"].setList(SongList) + if len(SongList) > 1: + self["list"].moveToIndex(1) + + + def buildArtistSongList(self, artistID, mode, addToCache): + if addToCache: + self.cacheList.append(CacheList(index = self["list"].getCurrentIndex(), listview = self["list"].getList(), headertext = self["headertext"].getText(), methodarguments = self.LastMethod)) + arguments = {} + arguments["artistID"] = artistID + arguments["mode"] = mode + arguments["addToCache"] = False + self.LastMethod = MethodArguments(method = self.buildArtistSongList, arguments = arguments) + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + artistSongList = [] + artistSongList.append((Item(text = _("[back]"), mode = mode, navigator = True),)) + cursor.execute("select song_id, title, artists.artist, filename, bitrate, length, genre_text, track, date, album_text, songs.Album_id from songs inner join artists on songs.artist_id = artists.artist_id inner join Album on songs.Album_id = Album.Album_id inner join genre on songs.genre_id = genre.genre_id where songs.artist_id = %d order by Album.album_text, tracknumber, filename;" % (artistID)) + for row in cursor: + artistSongList.append((Item(mode = 99, songID = row[0], title = row[1], artist = row[2], filename = row[3], bitrate = row[4], length = row[5], genre = row[6], track = row[7], date = row[8], album = row[9], albumID = row[10], artistID = artistID),)) + cursor.execute("SELECT artist from artists where artist_ID = %d;" % artistID) + row = cursor.fetchone() + self["headertext"].setText(_("Artist (%s) -> Song List") % row[0]) + cursor.close() + connection.close() + self["list"].setList(artistSongList) + if len(artistSongList) > 1: + self["list"].moveToIndex(1) + + def buildAlbumSongList(self, albumID, mode, addToCache): + if addToCache: + self.cacheList.append(CacheList(index = self["list"].getCurrentIndex(), listview = self["list"].getList(), headertext = self["headertext"].getText(), methodarguments = self.LastMethod)) + arguments = {} + arguments["albumID"] = albumID + arguments["mode"] = mode + arguments["addToCache"] = False + self.LastMethod = MethodArguments(method = self.buildAlbumSongList, arguments = arguments) + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + albumSongList = [] + albumSongList.append((Item(text = _("[back]"), mode = mode, navigator = True),)) + cursor.execute("select song_id, title, artists.artist, filename, songs.artist_id, bitrate, length, genre_text, track, date, album_text from songs inner join artists on songs.artist_id = artists.artist_id inner join Album on songs.Album_id = Album.Album_id inner join genre on songs.genre_id = genre.genre_id where songs.album_id = %d order by tracknumber, filename;" % (albumID)) + for row in cursor: + albumSongList.append((Item(mode = 99, songID = row[0], title = row[1], artist = row[2], filename = row[3], artistID = row[4], bitrate = row[5], length = row[6], genre = row[7], track = row[8], date = row[9], album = row[10], albumID = albumID),)) + cursor.execute("SELECT album_text from album where album_ID = %d;" % albumID) + row = cursor.fetchone() + self["headertext"].setText(_("Album (%s) -> Song List") % row[0]) + cursor.close() + connection.close() + self["list"].setList(albumSongList) + if len(albumSongList) > 1: + self["list"].moveToIndex(1) + + def buildMainMenuList(self, addToCache = True): + arguments = {} + arguments["addToCache"] = True + self.LastMethod = MethodArguments(method = self.buildMainMenuList, arguments = arguments) + self["headertext"].setText(_("iDream Main Menu")) + mainMenuList = [] + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + # 1. Playlists + cursor.execute("SELECT COUNT (*) FROM playlists;") + row = cursor.fetchone() + mainMenuList.append((Item(text = _("Playlists (%d)") % row[0], mode = 1),)) + # 2. Artists + cursor.execute("SELECT COUNT (*) FROM artists;") + row = cursor.fetchone() + mainMenuList.append((Item(text = _("Artists (%d)") % row[0], mode = 4),)) + # 3. Albums + cursor.execute("SELECT COUNT (DISTINCT album_text) FROM album;") + row = cursor.fetchone() + mainMenuList.append((Item(text = _("Albums (%d)") % row[0], mode = 7),)) + # 4. Songs + cursor.execute("SELECT COUNT (*) FROM songs;") + row = cursor.fetchone() + mainMenuList.append((Item(text = _("Songs (%d)") % row[0], mode = 10),)) + # 5. Genres + cursor.execute("SELECT COUNT (*) FROM genre;") + row = cursor.fetchone() + mainMenuList.append((Item(text = _("Genres (%d)") % row[0], mode = 13),)) + cursor.close() + connection.close() + self["list"].setList(mainMenuList) + self["list"].moveToIndex(0) + + def buildArtistList(self, addToCache): + if addToCache: + self.cacheList.append(CacheList(index = self["list"].getCurrentIndex(), listview = self["list"].getList(), headertext = self["headertext"].getText(), methodarguments = self.LastMethod)) + arguments = {} + arguments["addToCache"] = False + self.LastMethod = MethodArguments(method = self.buildArtistList, arguments = arguments) + self["headertext"].setText(_("Artists List")) + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + artistList = [] + artistList.append((Item(text = _("[back]"), mode = 0, navigator = True),)) + cursor.execute("SELECT artists.artist_id,artists.artist, count (distinct album.album_text) FROM songs INNER JOIN artists ON songs.artist_id = artists.artist_id inner join album on songs.album_id = album.album_id GROUP BY songs.artist_id ORDER BY artists.artist;") + for row in cursor: + artistList.append((Item(text = "%s (%d)" % (row[1], row[2]), mode = 5, artistID = row[0]),)) + cursor.close() + connection.close() + self["list"].setList(artistList) + + def buildArtistAlbumList(self, ArtistID, addToCache): + if addToCache: + self.cacheList.append(CacheList(index = self["list"].getCurrentIndex(), listview = self["list"].getList(), headertext = self["headertext"].getText(), methodarguments = self.LastMethod)) + arguments = {} + arguments["ArtistID"] = ArtistID + arguments["addToCache"] = False + self.LastMethod = MethodArguments(method = self.buildArtistAlbumList, arguments = arguments) + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + albumArtistList = [] + albumArtistList.append((Item(text = _("[back]"), mode = 4, navigator = True),)) + cursor.execute("select Album.Album_id,Album.Album_text from songs inner join Album on songs.Album_id = Album.Album_id where songs.artist_id = %d group by songs.Album_id order by Album.Album_text;" % ArtistID) + for row in cursor: + cursor2 = connection.cursor() + cursor2.execute("select count(song_id) from songs where album_id = %d;" % row[0]) + row2 = cursor2.fetchone() + albumArtistList.append((Item(text = "%s (%d)" % (row[1], row2[0]), mode = 6, albumID = row[0], artistID = ArtistID),)) + cursor2.close() + cursor.execute("SELECT artist from artists where artist_ID = %d;" % ArtistID) + row = cursor.fetchone() + self["headertext"].setText(_("Artist (%s) -> Album List") % row[0]) + cursor.close() + connection.close() + self["list"].setList(albumArtistList) + if len(albumArtistList) > 1: + self["list"].moveToIndex(1) + + def buildAlbumList(self, addToCache): + if addToCache: + self.cacheList.append(CacheList(index = self["list"].getCurrentIndex(), listview = self["list"].getList(), headertext = self["headertext"].getText(), methodarguments = self.LastMethod)) + arguments = {} + arguments["addToCache"] = False + self.LastMethod = MethodArguments(method = self.buildAlbumList, arguments = arguments) + self["headertext"].setText(_("Albums List")) + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + albumList = [] + albumList.append((Item(text = _("[back]"), mode = 0, navigator = True),)) + cursor.execute("select Album.Album_id,Album.Album_text, count(*) from songs inner join Album on songs.Album_id = Album.Album_id group by songs.Album_id order by Album.Album_text;") + for row in cursor: + albumList.append((Item(text = "%s (%d)" % (row[1], row[2]), mode = 8, albumID = row[0]),)) + cursor.close() + connection.close() + self["list"].setList(albumList) + if len(albumList) > 1: + self["list"].moveToIndex(1) + + def startRun(self): + if pathToDatabase.isRunning: + self.showScanner = eTimer() + self.showScanner.callback.append(self.showScannerCallback) + self.showScanner.start(0,1) + else: + if config.plugins.merlinmusicplayer.startlastsonglist.value: + self.startPlayerTimer = eTimer() + self.startPlayerTimer.callback.append(self.startPlayerTimerCallback) + self.startPlayerTimer.start(0,1) + self.mode = 0 + self["list"].setMode(self.mode) + self.buildMainMenuList() + + def showScannerCallback(self): + self.session.openWithCallback(self.filesAdded, iDreamAddToDatabase,None) + + + def startPlayerTimerCallback(self): + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + iDreamMode = False + SongList = [] + cursor.execute("select song_id, filename, title, artist, album, genre, bitrate, length, track, date, PTS from CurrentSongList;") + for row in cursor: + SongList.append((Item(songID = row[0], text = os_path.basename(row[1]), filename = row[1], title = row[2], artist = row[3], album = row[4], genre = row[5], bitrate = row[6], length = row[7], track = row[8], date = row[9], PTS = row[10], join = False),)) + if row[0] != 0: + iDreamMode = True + cursor.close() + connection.close() + if self.player is not None: + self.player.doClose() + self.player = None + count = len(SongList) + if count: + # just to be sure, check the index , it's critical + index = config.plugins.merlinmusicplayer.lastsonglistindex.value + if index >= count: + index = 0 + self.player = self.session.instantiateDialog(MerlinMusicPlayerScreen,SongList, index, iDreamMode) + self.session.execDialog(self.player) + + def config(self): + self.session.openWithCallback(self.setupFinished, MerlinMusicPlayerSetup, True) + + def setupFinished(self, result): + if result: + self.red_pressed() + + def Error(self, error = None): + if error is not None: + self["list"].hide() + self["statustext"].setText(str(error.getErrorMessage())) + + def closing(self): + self.close() + + def __onClose(self): + if self.player is not None: + self.player.doClose() + self.player = None + self.session.nav.playService(self.CurrentService) + + def lcdUpdate(self): + try: + count = self["list"].getItemCount() + index = self["list"].getCurrentIndex() + iDreamList = self["list"].getList() + self.summaries.setText(iDreamList[index][0].title or iDreamList[index][0].text,1) + # voheriges + index -= 1 + if index < 0: + index = count + self.summaries.setText(iDreamList[index][0].title or iDreamList[index][0].text,3) + # naechstes + index = self["list"].getCurrentIndex() + 1 + if index > count: + index = 0 + self.summaries.setText(iDreamList[index][0].title or iDreamList[index][0].text,4) + except: pass + + def createSummary(self): + return MerlinMusicPlayerLCDScreenText + + +class iDreamList(GUIComponent, object): + def buildEntry(self, item): + width = self.l.getItemSize().width() + res = [ None ] + if self.displaySongMode: + if item.navigator: + res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 3, width , 20, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, "%s" % item.text)) + else: + res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 3, width - 100 , 20, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, "%s - %s" % (item.title, item.artist))) + res.append((eListboxPythonMultiContent.TYPE_TEXT, width - 100,3,100, 20, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, "%s" % item.track)) + res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 26,width -200, 18, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, "%s%s" % (item.album, item.date))) + res.append((eListboxPythonMultiContent.TYPE_TEXT, width -200, 26,200, 18, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, "%s" % item.length)) + res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 47,width -200, 18, 1, RT_HALIGN_LEFT|RT_VALIGN_CENTER, "%s" % item.genre)) + res.append((eListboxPythonMultiContent.TYPE_TEXT, width -200, 47,200, 18, 1, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, "%s" % item.bitrate)) + else: + if item.navigator: + res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 3, width , 20, 0, RT_HALIGN_CENTER|RT_VALIGN_CENTER, "%s" % item.text)) + else: + if item.PTS is None: + res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 3, width , 20, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, "%s" % item.text)) + else: + res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 3, width , 20, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, "%s" % item.title)) + + return res + + def __init__(self): + GUIComponent.__init__(self) + self.l = eListboxPythonMultiContent() + self.l.setBuildFunc(self.buildEntry) + self.l.setFont(0, gFont("Regular", 20)) + self.l.setFont(1, gFont("Regular", 16)) + self.l.setItemHeight(22) + self.onSelectionChanged = [ ] + self.mode = 0 + self.displaySongMode = False + self.list = [] + self.itemCount = 0 + + def connectSelChanged(self, fnc): + if not fnc in self.onSelectionChanged: + self.onSelectionChanged.append(fnc) + + def disconnectSelChanged(self, fnc): + if fnc in self.onSelectionChanged: + self.onSelectionChanged.remove(fnc) + + def selectionChanged(self): + for x in self.onSelectionChanged: + x() + + def getCurrent(self): + cur = self.l.getCurrentSelection() + return cur and cur[0] + + GUI_WIDGET = eListbox + + def postWidgetCreate(self, instance): + instance.setContent(self.l) + instance.selectionChanged.get().append(self.selectionChanged) + + def preWidgetRemove(self, instance): + instance.setContent(None) + instance.selectionChanged.get().remove(self.selectionChanged) + + def moveToIndex(self, index): + self.instance.moveSelectionTo(index) + + def getCurrentIndex(self): + return self.instance.getCurrentIndex() + + currentIndex = property(getCurrentIndex, moveToIndex) + currentSelection = property(getCurrent) + + def setList(self, list): + self.list = list + self.l.setList(list) + self.itemCount = len(self.list) - 1 + + def getItemCount(self): + return self.itemCount + + def getList(self): + return self.list + + def removeItem(self, index): + del self.list[index] + self.l.entryRemoved(index) + + def getDisplaySongMode(self): + return self.displaySongMode + + def setMode(self, mode): + self.mode = mode + if mode == 2 or mode == 6 or mode == 8 or mode == 10 or mode == 18 or mode == 19 or mode == 14 or mode == 20: + self.displaySongMode = True + self.l.setItemHeight(68) + else: + self.displaySongMode = False + self.l.setItemHeight(22) + + +class MerlinMediaPixmap(Pixmap): + def __init__(self): + Pixmap.__init__(self) + self.coverArtFileName = "" + self.picload = ePicLoad() + self.picload.PictureData.get().append(self.paintCoverArtPixmapCB) + self.coverFileNames = ["folder.png", "folder.jpg", "cover.jpg", "cover.png", "coverArt.jpg"] + + def applySkin(self, desktop, screen): + from Tools.LoadPixmap import LoadPixmap + noCoverFile = None + if self.skinAttributes is not None: + for (attrib, value) in self.skinAttributes: + if attrib == "pixmap": + noCoverFile = value + break + if noCoverFile is None: + noCoverFile = resolveFilename(SCOPE_CURRENT_SKIN, "skin_default/no_coverArt.png") + self.noCoverPixmap = LoadPixmap(noCoverFile) + return Pixmap.applySkin(self, desktop, screen) + + def onShow(self): + Pixmap.onShow(self) + sc = AVSwitch().getFramebufferScale() + self.picload.setPara((self.instance.size().width(), self.instance.size().height(), sc[0], sc[1], False, 1, "#00000000")) + + def paintCoverArtPixmapCB(self, picInfo=None): + ptr = self.picload.getData() + if ptr != None: + self.instance.setPixmap(ptr.__deref__()) + + def updateCoverArt(self, path): + back = False + while not path.endswith("/"): + path = path[:-1] + new_coverArtFileName = None + for filename in self.coverFileNames: + if fileExists(path + filename): + new_coverArtFileName = path + filename + if self.coverArtFileName != new_coverArtFileName: + if new_coverArtFileName: + self.coverArtFileName = new_coverArtFileName + print "[MerlinMusicPlayer] using cover from %s " % self.coverArtFileName + self.picload.startDecode(self.coverArtFileName) + back = True + else: + if new_coverArtFileName: + back = True + return back + + def showDefaultCover(self): + self.coverArtFileName = "" + self.instance.setPixmap(self.noCoverPixmap) + + def showCoverFromFile(self, filename): + self.coverArtFileName = filename + self.picload.startDecode(self.coverArtFileName) + + def embeddedCoverArt(self): + print "[embeddedCoverArt] found" + self.coverArtFileName = "/tmp/.id3coverart" + self.picload.startDecode(self.coverArtFileName) + + +class SelectPath(Screen): + skin = """ + + + + + + + + + """ + def __init__(self, session, initDir): + Screen.__init__(self, session) + inhibitDirs = ["/bin", "/boot", "/dev", "/etc", "/lib", "/proc", "/sbin", "/sys", "/usr", "/var"] + inhibitMounts = [] + self["filelist"] = FileList(initDir, showDirectories = True, showFiles = False, inhibitMounts = inhibitMounts, inhibitDirs = inhibitDirs) + self["target"] = Label() + self["actions"] = ActionMap(["WizardActions", "DirectionActions", "ColorActions", "EPGSelectActions"], + { + "back": self.cancel, + "left": self.left, + "right": self.right, + "up": self.up, + "down": self.down, + "ok": self.ok, + "green": self.green, + "red": self.cancel + + }, -1) + self["key_red"] = StaticText(_("Cancel")) + self["key_green"] = StaticText(_("OK")) + + def cancel(self): + self.close(None) + + def green(self): + self.close(self["filelist"].getSelection()[0]) + + def up(self): + self["filelist"].up() + self.updateTarget() + + def down(self): + self["filelist"].down() + self.updateTarget() + + def left(self): + self["filelist"].pageUp() + self.updateTarget() + + def right(self): + self["filelist"].pageDown() + self.updateTarget() + + def ok(self): + if self["filelist"].canDescent(): + self["filelist"].descent() + self.updateTarget() + + def updateTarget(self): + currFolder = self["filelist"].getSelection()[0] + if currFolder is not None: + self["target"].setText(currFolder) + else: + self["target"].setText(_("Invalid Location")) + + +class MerlinMusicPlayerLCDScreen(Screen): + skin = """ + + + Position,ShowHours + + + + """ + + def __init__(self, session, parent): + Screen.__init__(self, session) + self["text1"] = Label() + self["text4"] = Label() + + def setText(self, text, line): + if line == 1: + self["text1"].setText(text) + elif line == 4: + self["text4"].setText(text) + +class MerlinMusicPlayerLCDScreenText(Screen): + skin = """ + + + + + """ + + def __init__(self, session, parent): + Screen.__init__(self, session) + self["text1"] = Label() + self["text3"] = Label() + self["text4"] = Label() + + def setText(self, text, line): + textleer = " " + text = text + textleer*10 + if line == 1: + self["text1"].setText(text) + elif line == 3: + self["text3"].setText(text) + elif line == 4: + self["text4"].setText(text) + + +class MerlinMusicPlayerSetup(Screen, ConfigListScreen): + + sz_w = getDesktop(0).size().width() + if sz_w == 1280: + skin = """ + + + + + + + + """ + + elif sz_w == 1024: + skin = """ + + + + + + + """ + else: + skin = """ + + + + + + + """ + + + def __init__(self, session, databasePath): + Screen.__init__(self, session) + + self["key_red"] = StaticText(_("Cancel")) + self["key_green"] = StaticText(_("OK")) + + self.list = [ ] + if HardwareInfo().get_device_name() != "dm7025": + self.list.append(getConfigListEntry(_("Use hardware-decoder for MP3"), config.plugins.merlinmusicplayer.hardwaredecoder)) + self.list.append(getConfigListEntry(_("Play last used songlist after starting"), config.plugins.merlinmusicplayer.startlastsonglist)) + if databasePath: + self.database = getConfigListEntry(_("iDream database path"), config.plugins.merlinmusicplayer.databasepath) + self.list.append(self.database) + else: + self.database = None + self.list.append(getConfigListEntry(_("Use google-images for cover art"), config.plugins.merlinmusicplayer.usegoogleimage)) + self.googleimage = getConfigListEntry(_("Google image path"), config.plugins.merlinmusicplayer.googleimagepath) + self.list.append(self.googleimage) + self.list.append(getConfigListEntry(_("Activate screensaver"), config.plugins.merlinmusicplayer.usescreensaver)) + self.list.append(getConfigListEntry(_("Wait for screensaver (in min)"), config.plugins.merlinmusicplayer.screensaverwait)) + self.list.append(getConfigListEntry(_("Remember last path of filebrowser"), config.plugins.merlinmusicplayer.rememberlastfilebrowserpath)) + self.defaultFileBrowserPath = getConfigListEntry(_("Filebrowser startup path"), config.plugins.merlinmusicplayer.defaultfilebrowserpath) + self.list.append(self.defaultFileBrowserPath) + self.list.append(getConfigListEntry(_("Show iDream in extended-pluginlist"), config.plugins.merlinmusicplayer.idreamextendedpluginlist)) + self.list.append(getConfigListEntry(_("Show Merlin Music Player in extended-pluginlist"), config.plugins.merlinmusicplayer.merlinmusicplayerextendedpluginlist)) + + ConfigListScreen.__init__(self, self.list, session) + self["setupActions"] = ActionMap(["SetupActions", "ColorActions"], + { + "green": self.keySave, + "cancel": self.keyClose, + "ok": self.keySelect, + }, -2) + + def keySelect(self): + cur = self["config"].getCurrent() + if cur == self.database: + self.session.openWithCallback(self.pathSelectedDatabase,SelectPath,config.plugins.merlinmusicplayer.databasepath.value) + elif cur == self.defaultFileBrowserPath: + self.session.openWithCallback(self.pathSelectedFilebrowser,SelectPath,config.plugins.merlinmusicplayer.defaultfilebrowserpath.value) + elif cur == self.googleimage: + self.session.openWithCallback(self.pathSelectedGoogleImage,SelectPath,config.plugins.merlinmusicplayer.googleimagepath.value) + + def pathSelectedGoogleImage(self, res): + if res is not None: + config.plugins.merlinmusicplayer.googleimagepath.value = res + + def pathSelectedDatabase(self, res): + if res is not None: + config.plugins.merlinmusicplayer.databasepath.value = res + + def pathSelectedFilebrowser(self, res): + if res is not None: + config.plugins.merlinmusicplayer.defaultfilebrowserpath.value = res + + def keySave(self): + for x in self["config"].list: + x[1].save() + configfile.save() + self.close(True) + + def keyClose(self): + for x in self["config"].list: + x[1].cancel() + self.close(False) + + + +class MerlinMusicPlayerFileList(Screen): + + sz_w = getDesktop(0).size().width() + if sz_w == 1280: + skin = """ + + + + + + + + """ + elif sz_w == 1024: + skin = """ + + + + + + + """ + else: + skin = """ + + + + + + + + """ + + + def __init__(self, session): + self.session = session + Screen.__init__(self, session) + self["list"] = FileList(config.plugins.merlinmusicplayer.defaultfilebrowserpath.value, showDirectories = True, showFiles = True, matchingPattern = "(?i)^.*\.(mp3|m4a|flac|ogg|m3u|pls|cue)", useServiceRef = False) + + + self["actions"] = ActionMap(["WizardActions", "DirectionActions", "ColorActions", "EPGSelectActions"], + { + "ok": self.ok, + "back": self.close, + "input_date_time": self.config, + "info" : self.info_pressed, + }, -1) + + self["headertext"] = Label(_("Filelist")) + self.player = None + self.onClose.append(self.__onClose) + self.onLayoutFinish.append(self.startRun) + self.CurrentService = self.session.nav.getCurrentlyPlayingServiceReference() + self.session.nav.stopService() + + def startRun(self): + if config.plugins.merlinmusicplayer.startlastsonglist.value: + self.startPlayerTimer = eTimer() + self.startPlayerTimer.callback.append(self.startPlayerTimerCallback) + self.startPlayerTimer.start(0,1) + + def startPlayerTimerCallback(self): + connection = OpenDatabase() + connection.text_factory = str + cursor = connection.cursor() + iDreamMode = False + SongList = [] + cursor.execute("select song_id, filename, title, artist, album, genre, bitrate, length, track, date, PTS from CurrentSongList;") + for row in cursor: + SongList.append((Item(songID = row[0], text = os_path.basename(row[1]), filename = row[1], title = row[2], artist = row[3], album = row[4], genre = row[5], bitrate = row[6], length = row[7], track = row[8], date = row[9], PTS = row[10], join = False),)) + if row[0] != 0: + iDreamMode = True + cursor.close() + connection.close() + if self.player is not None: + self.player.doClose() + self.player = None + count = len(SongList) + if count: + # just to be sure, check the index , it's critical + index = config.plugins.merlinmusicplayer.lastsonglistindex.value + if index >= count: + index = 0 + self.player = self.session.instantiateDialog(MerlinMusicPlayerScreen,SongList, index, iDreamMode) + self.session.execDialog(self.player) + + def readCUE(self, filename): + SongList = [] + displayname = None + try: + cuefile = open(filename, "r") + except IOError: + return None + import re + performer_re = re.compile(r"""PERFORMER "(?P.*?)"(?:=\r\n|\r|\n|$)""") + title_re = re.compile(r"""TITLE "(?P.*?)"(?:=\r\n|\r|\n|$)""") + filename_re = re.compile(r"""FILE "(?P<filename>.+?)".*(?:=\r\n|\r|\n|$)""", re.DOTALL) + track_re = re.compile(r"""TRACK (?P<track_number>[^ ]+?)(?:[ ]+.*?)(?:=\r\n|\r|\n|$)""") + index_re = re.compile(r"""INDEX (?P<index_nr>[^ ]+?)[ ]+(?P<track_index>[^ ]+?)(?:=\r\n|\r|\n|$)""") + msts_re = re.compile("""^(?P<mins>[0-9]{1,}):(?P<secs>[0-9]{2}):(?P<ms>[0-9]{2})$""") + songfilename = "" + album = "" + performer = "" + title = "" + pts = 0 + state = 0 # header + for line in cuefile.readlines(): + entry = line.strip() + m = filename_re.search(entry) + if m: + if m.group('filename')[0] == "/": + songfilename = m.group('filename') + else: + songfilename = os_path.dirname(filename) + "/" + m.group('filename') + m = title_re.search(entry) + if m: + if state == 0: + album = m.group('title') + else: + title = m.group('title') + m = performer_re.search(entry) + if m: + performer = m.group('performer') + m = track_re.search(entry) + if m: + state = 1 # tracks + m = index_re.search(entry) + if m: + if int(m.group('index_nr')) == 1: + m1 = msts_re.search(m.group('track_index')) + if m1: + pts = (int(m1.group('mins')) * 60 + int(m1.group('secs'))) * 90000 + SongList.append((Item(text = title, filename = songfilename, title = title, artist = performer, album = album,join = False, PTS = pts),)) + cuefile.close() + return SongList + + def readM3U(self, filename): + SongList = [] + displayname = None + try: + m3ufile = open(filename, "r") + except IOError: + return None + for line in m3ufile.readlines(): + entry = line.strip() + if entry != "": + if entry.startswith("#EXTINF:"): + extinf = entry.split(',',1) + if len(extinf) > 1: + displayname = extinf[1] + elif entry[0] != "#": + if entry[0] == "/": + songfilename = entry + else: + songfilename = os_path.dirname(filename) + "/" + entry + if displayname: + text = displayname + displayname = None + else: + text = entry + SongList.append((Item(text = text, filename = songfilename),)) + m3ufile.close() + return SongList + + def readPLS(self, filename): + SongList = [] + displayname = None + try: + plsfile = open(filename, "r") + except IOError: + return None + entry = plsfile.readline().strip() + if entry == "[playlist]": + while True: + entry = plsfile.readline().strip() + if entry == "": + break + if entry[0:4] == "File": + pos = entry.find('=') + 1 + newentry = entry[pos:] + SongList.append((Item(text = newentry, filename = newentry),)) + else: + SongList = self.readM3U(filename) + plsfile.close() + return SongList + + def ok(self): + if self["list"].canDescent(): + self["list"].descent() + else: + SongList = [] + index = 0 + foundIndex = 0 + count = 0 + currentFilename = self["list"].getFilename() + if currentFilename.lower().endswith(".m3u"): + SongList = self.readM3U(os_path.join(self["list"].getCurrentDirectory(),currentFilename)) + elif currentFilename.lower().endswith(".pls"): + SongList = self.readPLS(os_path.join(self["list"].getCurrentDirectory(),currentFilename)) + elif currentFilename.lower().endswith(".cue"): + SongList = self.readCUE(os_path.join(self["list"].getCurrentDirectory(),currentFilename)) + else: + files = os_listdir(self["list"].getCurrentDirectory()) + files.sort() + for filename in files: + if filename.lower().endswith(".mp3") or filename.lower().endswith(".flac") or filename.lower().endswith(".m4a") or filename.lower().endswith(".ogg"): + SongList.append((Item(text = filename, filename = os_path.join(self["list"].getCurrentDirectory(),filename)),)) + if self["list"].getFilename() == filename: + foundIndex = index + index += 1 + if self.player is not None: + self.player.doClose() + self.player = None + count = len(SongList) + if count: + self.player = self.session.instantiateDialog(MerlinMusicPlayerScreen,SongList, foundIndex, False) + self.session.execDialog(self.player) + else: + self.session.open(MessageBox, _("No music files found!"), type = MessageBox.TYPE_INFO,timeout = 20 ) + + def config(self): + self.session.open(MerlinMusicPlayerSetup, True) + + def info_pressed(self): + if self.player is not None: + if self.player.songList: + self.session.execDialog(self.player) + + def __onClose(self): + if self.player is not None: + self.player.doClose() + self.player = None + self.session.nav.playService(self.CurrentService) + if config.plugins.merlinmusicplayer.rememberlastfilebrowserpath.value: + config.plugins.merlinmusicplayer.defaultfilebrowserpath.value = self["list"].getCurrentDirectory() + config.plugins.merlinmusicplayer.defaultfilebrowserpath.save() + + +def main(session,**kwargs): + session.open(iDreamMerlin) + +def merlinmusicplayerfilelist(session,**kwargs): + session.open(MerlinMusicPlayerFileList) + +def Plugins(**kwargs): + + list = [PluginDescriptor(name="Merlin iDream", description=_("Dreambox Music Database"), where = [PluginDescriptor.WHERE_PLUGINMENU], icon = "iDream.png", fnc=main)] + list.append(PluginDescriptor(name="Merlin Music Player", description=_("Merlin Music Player"), where = [PluginDescriptor.WHERE_PLUGINMENU], icon = "MerlinMusicPlayer.png", fnc=merlinmusicplayerfilelist)) + if config.plugins.merlinmusicplayer.idreamextendedpluginlist.value: + list.append(PluginDescriptor(name="iDream", description=_("Dreambox Music Database"), where = [PluginDescriptor.WHERE_EXTENSIONSMENU], fnc=main)) + if config.plugins.merlinmusicplayer.merlinmusicplayerextendedpluginlist.value: + list.append(PluginDescriptor(name="Merlin Music Player", description=_("Merlin Music Player"), where = [PluginDescriptor.WHERE_EXTENSIONSMENU], fnc=merlinmusicplayerfilelist)) + return list +