diff options
author | lvw <lvw@e10066b5-e1e2-0310-b819-94efdf66514b> | 2004-09-07 17:40:47 +0000 |
---|---|---|
committer | lvw <lvw@e10066b5-e1e2-0310-b819-94efdf66514b> | 2004-09-07 17:40:47 +0000 |
commit | 653ca204d10b3481670a80769baa3d768bb5f64a (patch) | |
tree | 97845708a0991d68ac10ec36618fe5fbd6eb584b | |
parent | 22cedb0c0bb5345d92b67216f792810e75c75241 (diff) | |
download | vdr-plugin-muggle-653ca204d10b3481670a80769baa3d768bb5f64a.tar.gz vdr-plugin-muggle-653ca204d10b3481670a80769baa3d768bb5f64a.tar.bz2 |
Merged ogg vorbis player to trunk
git-svn-id: https://vdr-muggle.svn.sourceforge.net/svnroot/vdr-muggle/trunk/muggle-plugin@148 e10066b5-e1e2-0310-b819-94efdf66514b
-rw-r--r-- | HISTORY | 6 | ||||
-rw-r--r-- | Makefile | 10 | ||||
-rw-r--r-- | TODO | 46 | ||||
-rw-r--r-- | gd_content_interface.c | 69 | ||||
-rw-r--r-- | gd_content_interface.h | 84 | ||||
-rwxr-xr-x | mg_content_interface.c | 25 | ||||
-rwxr-xr-x | mg_content_interface.h | 75 | ||||
-rw-r--r-- | mg_tools.h | 28 | ||||
-rw-r--r-- | muggle.h | 26 | ||||
-rwxr-xr-x | mugglei.c | 690 | ||||
-rwxr-xr-x | scripts/createtables.mysql | 4 | ||||
-rw-r--r-- | vdr_decoder.c | 73 | ||||
-rw-r--r-- | vdr_decoder.h | 25 | ||||
-rw-r--r-- | vdr_decoder_mp3.c | 22 | ||||
-rw-r--r-- | vdr_decoder_mp3.h | 13 | ||||
-rw-r--r-- | vdr_decoder_ogg.c | 393 | ||||
-rw-r--r-- | vdr_decoder_ogg.h | 60 | ||||
-rw-r--r-- | vdr_player.c | 43 |
18 files changed, 1150 insertions, 542 deletions
@@ -1,6 +1,8 @@ VDR Plugin 'muggle' Revision History ------------------------------------ -2004-01-15: Version 0.0.1 +2004-08-31: Version 0.0.1-ALPHA + +- An initial revision given to a few people. + -- Initial revision. @@ -1,7 +1,7 @@ # # Makefile for a Video Disk Recorder plugin # -# $Id: Makefile,v 1.10 2004/08/29 14:39:33 lvw Exp $ +# $Id$ # The official name of this plugin. # This name will be used in the '-P...' option of VDR to load the plugin. @@ -21,7 +21,7 @@ CXXFLAGS ?= -O2 -Wall -Woverloaded-virtual -Wno-deprecated -g ### The directory environment: DVBDIR = ../../../../DVB -VDRDIR = ../../.. +VDRDIR = /usr/local/src/VDR LIBDIR = ../../lib TMPDIR = /tmp @@ -42,14 +42,14 @@ PACKAGE = vdr-$(ARCHIVE) INCLUDES += -I$(VDRDIR) -I$(VDRDIR)/include -I$(DVBDIR)/include -I/usr/include/mysql/ -DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' +DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' -DHAVE_VORBISFILE MIFLAGS += -I/usr/include/taglib -ltag -lmysqlclient ### The object files (add further files here): -OBJS = $(PLUGIN).o i18n.o vdr_menu.o mg_database.o mg_content_interface.o gd_content_interface.o mg_tools.o mg_media.o mg_filters.o mg_playlist.o vdr_decoder_mp3.o vdr_stream.o vdr_decoder.o vdr_player.o vdr_setup.o +OBJS = $(PLUGIN).o i18n.o vdr_menu.o mg_database.o mg_content_interface.o gd_content_interface.o mg_tools.o mg_media.o mg_filters.o mg_playlist.o vdr_decoder_mp3.o vdr_stream.o vdr_decoder.o vdr_player.o vdr_setup.o vdr_decoder_ogg.o -LIBS = -lmad -lmysqlclient +LIBS = -lmad -lmysqlclient -lvorbisfile -lvorbis ### Targets: @@ -1,6 +1,12 @@ TODO File for Muggle ==================== +Ogg/Vorbis decoder integration +------------------------------ +- Think, whether type (mp3, ogg, flac) should be stored in database + +Deployment +---------- - Script to publish a version - Checkout - make dist @@ -9,14 +15,16 @@ TODO File for Muggle - copy into web directory - sync with web +- How to track bugs and feature requests? + Testing/bugs -============ +------------ - Test execution of playlist commands -- Test mgPCMPlayer::getSourceFile() for GD case (find) +- Keep this? Test mgPCMPlayer::getSourceFile() for GD case (find) - Test saving/loading playlists to database Import -====== +------ - Album - Cover text - Cover images (based on filename or tag) @@ -27,35 +35,32 @@ Import - Language (?) - Genre1, 2 - Rating? - - Bitrate - Modified, created - Lyrics - Playlist from m3u Code polishing -============== +-------------- +- Check for unnecessary log commands +- Generate HTML documentation using doxygen, + - use dotty/gv for state machines of player + - make available online +- Clean up mugglei - Check for memory leaks - Why do filters use pointers? - Check for (reasonably) consistent usage of char*/string + - mgDatabase is not used? - should handle a static object with a MySQL connection - execute queries? - - mgPlayer used what for? - Could save IP/host name and associate last playlist loaded -- Check for unnecessary log commands -- Generate HTML documentation using doxygen, - - use dotty/gv for state machines of player - - make available online - - Check compatibility for 1.3.12 Short term items ================ -- Import existing m3u playlists (in import) -- Import genres OSD in general -------------- @@ -148,6 +153,21 @@ Already Done - i18n (english and german) - Album import - Various artists +- Ogg/Vorbis decoder integration + - cOggFile kept + - cOggInfo dismissed in favor of obtaining info from DB + - coding conventions adapted + - Schema extended to keep audio properties + - Import (mugglei) extended to store audio properties in DB + (most notably samplerate, no. channels) + - Extended mgContentItem with audio properties + - Extended mgGdTrack with audio properties (bitrate, samplerate, channels) + - in mgPCMPlayer/vdr_player.c: + - pass m_playing to mp3/ogg decoder (instead of filename) + - mgOggDecoder: obtain audio properties from DB (channels, sampling rate via mgContentItem) + - mgPCMPlayer::getSourceFile moved to abstract data layer (mgContentItem) + and made concrete in subclasses (mgGdTrack) + - mgDecoders::findDecoder: extend decoder detection ************************************************************ * diff --git a/gd_content_interface.c b/gd_content_interface.c index 8df7c48..6c2f496 100644 --- a/gd_content_interface.c +++ b/gd_content_interface.c @@ -1,10 +1,11 @@ /*! \file gd_content_interface.c * \brief Data Objects for content (e.g. mp3 files, movies) for the vdr muggle plugin + * \ingroup giantdisc * * \version $Revision: 1.27 $ - * \date $Date: 2004/08/30 14:31:43 $ + * \date $Date$ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author: LarsAC $ + * \author Responsible author: $Author$ * * Implements main classes of for content items and interfaces to SQL databases * @@ -293,35 +294,35 @@ bool mgGdTrack::readData() // note: this does not work with empty album or genre fields result = mgSqlReadQuery(&m_db, "SELECT tracks.artist, album.title, tracks.title, " - " tracks.mp3file, genre.genre, tracks.year, " - " tracks.rating, tracks.length " - "FROM tracks, album, genre " + "tracks.mp3file, genre.genre, tracks.year, " + "tracks.rating, tracks.length, tracks.samplerate, tracks.channels, tracks.bitrate " + "FROM tracks, album, genre " "WHERE tracks.id=%d " "AND album.cddbid=tracks.sourceid AND " - " genre.id=tracks.genre1", + "genre.id=tracks.genre1", m_uniqID); nrows = mysql_num_rows(result); nfields = mysql_num_fields(result); - if(nrows == 0) + if( nrows == 0 ) { - mgWarning("No entries found \n"); + mgWarning( "No entries found \n" ); return false; } else { - if( nrows > 1 ) - { - mgWarning("mgGdTrack::readData: More than one entry found. Using first entry."); - } + if( nrows > 1 ) + { + mgWarning("mgGdTrack::readData: More than one entry found. Using first entry."); + } MYSQL_ROW row = mysql_fetch_row(result); m_artist = row[0]; m_album = row[1]; m_title = row[2]; - m_mp3file = row[3]; - m_genre = row[4]; + m_mp3file = string( the_setup.ToplevelDir ) + row[3]; + m_genre = row[4]; if( sscanf( row[5], "%d", &m_year) != 1 ) { @@ -338,6 +339,18 @@ bool mgGdTrack::readData() mgError( "Invalid duration '%s' in database", row [7]); } + if( row[8] && sscanf( row[8], "%d", &m_samplerate ) != 1 ) + { + mgError( "Invalid samplerate '%s' in database", row [7]); + } + + if( row[9] && sscanf( row[9], "%d", &m_channels ) != 1 ) + { + mgError( "Invalid channels '%s' in database", row [7]); + } + + m_bitrate = row[10]; + } m_retrieved = true; return true; @@ -445,6 +458,34 @@ int mgGdTrack::getDuration() } return m_rating; } + +int mgGdTrack::getSampleRate() +{ + if(!m_retrieved) + { + readData(); + } + return m_samplerate; +} + +int mgGdTrack::getChannels() +{ + if(!m_retrieved) + { + readData(); + } + return m_channels; +} + +string mgGdTrack::getBitrate() +{ + if(!m_retrieved) + { + readData(); + } + return m_bitrate; +} + string mgGdTrack::getImageFile() { return "dummyImg.jpg"; diff --git a/gd_content_interface.h b/gd_content_interface.h index 5d2626b..d810fa5 100644 --- a/gd_content_interface.h +++ b/gd_content_interface.h @@ -2,11 +2,12 @@ * \file gd_content_interface.h * \brief Data objects for content (e.g. mp3 files, movies) * for the vdr muggle plugin database + * \ingroup giantdisc * * \version $Revision: 1.11 $ - * \date $Date: 2004/08/30 14:31:43 $ + * \date $Date$ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author: LarsAC $ + * \author Responsible author: $Author$ * * Declares main classes for content items and interfaces to SQL databases * @@ -35,26 +36,28 @@ /*! * \brief Initialize a database for GD use + * \ingroup giantdisc * \todo Should be a static member of some GD class */ int GdInitDatabase(MYSQL *db); /*! * \brief Obtain the playlists stored within the GD schema + * \ingroup giantdisc * \todo Should be a static member of some GD class */ std::vector<std::string> *GdGetStoredPlaylists(MYSQL db); /*! * \brief A set of filters to search for content + * \ingroup giantdisc */ class gdFilterSets : public mgFilterSets { public: - /*! \addtogroup Object creation and destruction */ - /*\@{*/ + //\@{ /*! * \brief the default constructor @@ -69,7 +72,7 @@ class gdFilterSets : public mgFilterSets */ virtual ~gdFilterSets(); - /*\@}*/ + //\@ /*! * \brief compute restriction w.r.t active filters @@ -86,7 +89,8 @@ class gdFilterSets : public mgFilterSets /*! - * \brief represents a a single track + * \brief represents a a single track + * \ingroup giantdisc * * This may be any content item. e.g. a mp3 fileselection * The object is initially created with a database identifier. @@ -100,8 +104,7 @@ class mgGdTrack : public mgContentItem { public: - /*! \addtogroup Object creation and destruction */ - /*\@{*/ + //@{ /*! * \brief a constructor @@ -137,7 +140,7 @@ class mgGdTrack : public mgContentItem */ virtual ~mgGdTrack(); - /*\@}*/ + //@} /*! * \brief obtain the content type of the item @@ -157,8 +160,7 @@ class mgGdTrack : public mgContentItem return mgMediaPlayer(); } - /*! \addtogroup Data read access */ - /*\@{*/ + //\@{ /*! * \brief returns a certain field of the item as a string @@ -215,16 +217,26 @@ class mgGdTrack : public mgContentItem */ virtual int getRating(); + /*! \brief obtain the samplerate of the track + */ + virtual int getSampleRate(); + + /*! \brief obtain the number of audio channels of the track + */ + virtual int getChannels(); + + /*! \brief obtain the bitrate of the track + */ + virtual std::string getBitrate(); + /*! * \brief obtain the complete track information */ virtual std::vector<mgFilter*> *getTrackInfo(); - /*\@}*/ - - /*! \addtogroup Data write access */ - /*\@{*/ + //\@} + //\@{ /*! * \brief set the title of the track */ @@ -271,8 +283,7 @@ class mgGdTrack : public mgContentItem * Genre and album field information is lost. */ bool writeData(); - - /*\@}*/ + //\@} //! \brief a special instance denoting an undefined track static mgGdTrack UNDEFINED; @@ -319,6 +330,11 @@ private: std::string m_genre; /*! + * \brief The bitrate of the music + */ + std::string m_bitrate; + + /*! * \brief The year in which the track appeared */ int m_year; @@ -329,12 +345,21 @@ private: int m_rating; /*! - * \brief The length of the file in bytes. - * \todo TODO: is this true? Or what length are we talking about? + * \brief The length of the track in seconds */ int m_length; /*! + * \brief The sampling rate of the track in Hz + */ + int m_samplerate; + + /*! + * \brief The number of channels of the track + */ + int m_channels; + + /*! * \brief Access the data of the item from the database and fill actual data fields * * In order to avoid abundant queries to the database, the content fields @@ -348,6 +373,7 @@ private: /* * \brief a list of tracks stored in the databas + * \ingroup giantdisc */ class GdTracklist : public mgTracklist { @@ -365,9 +391,7 @@ class GdPlaylist : public mgPlaylist { public: - /*! \addtogroup Object creation and destruction */ - /*\@{*/ - + //@{ /*! * \brief opens an existing or creates empty playlist by name * @@ -386,7 +410,7 @@ class GdPlaylist : public mgPlaylist */ virtual ~GdPlaylist(); - /* \@} */ + //@} /*! * changes the listname of the playlist (and unset the sql id) @@ -430,6 +454,7 @@ class GdPlaylist : public mgPlaylist /*! * \brief hierarchical representation of a set of tracks + * \ingroup giantdisc * * The selection can be based on the whole database or a subset of it. * Within this selection, the data is organized in a tree hierarchy @@ -444,9 +469,7 @@ class GdTreeNode : public mgSelectionTreeNode { public: - /*! \addtogroup Object creation and destruction */ - /*\@{*/ - + //@{ /*! * \brief Construct a top level tree node in a certain view with certain filters */ @@ -462,12 +485,9 @@ public: * \brief destructor to clean up the object */ virtual ~GdTreeNode(); + //@} - /* \@} */ - - /*! \addtogroup Access node data */ - /*\@{*/ - + //@{ /*! * \brief check, whether the node is a leaf node (i.e. has no more children) */ @@ -488,7 +508,7 @@ public: */ virtual mgContentItem* getSingleTrack(); - /* \@} */ + //@} }; /* -------------------- begin CVS log --------------------------------- diff --git a/mg_content_interface.c b/mg_content_interface.c index c62bd2d..5c4890b 100755 --- a/mg_content_interface.c +++ b/mg_content_interface.c @@ -2,9 +2,9 @@ * \brief Data Objects for content (e.g. mp3 files, movies) for the vdr muggle plugin * * \version $Revision: 1.6 $ - * \date $Date: 2004/07/25 21:33:35 $ + * \date $Date$ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author: lvw $ + * \author Responsible author: $Author$ * * Implements main classes of for content items and interfaces to SQL databases * @@ -22,7 +22,7 @@ #define DUMMY /* constructor */ -mgContentItem mgContentItem::UNDEFINED = mgContentItem(); +mgContentItem mgContentItem::UNDEFINED = mgContentItem(); using namespace std; @@ -37,7 +37,7 @@ mgTracklist::mgTracklist() } /*! - * \brief destrucor + * \brief destructor * * Deletes all items in the tracklist and removes the list itself */ @@ -46,7 +46,7 @@ mgTracklist::~mgTracklist() mgContentItem* ptr; vector<mgContentItem*>::iterator iter; - for(iter = m_list.begin(); iter != m_list.end();iter++) + for( iter = m_list.begin(); iter != m_list.end(); iter++ ) { ptr = *iter; delete ptr; @@ -191,10 +191,8 @@ bool mgTracklist::remove(unsigned int position) } /*! - ***************************************************************************** * \brief remove all occurences of item - * - ****************************************************************************/ + */ int mgTracklist::remove(mgContentItem* item) { int retval = 0; @@ -211,13 +209,6 @@ int mgTracklist::remove(mgContentItem* item) return retval; } - - -/*=================================================================*/ -/* */ -/* class mgSelectionTreeNode */ -/* */ -/*=================================================================*/ mgSelectionTreeNode::mgSelectionTreeNode(MYSQL db, int view) { m_db = db; @@ -239,7 +230,7 @@ mgSelectionTreeNode::mgSelectionTreeNode(mgSelectionTreeNode* parent, string id, m_expanded = false; } - /*==== destructor ====*/ +/*==== destructor ====*/ mgSelectionTreeNode::~mgSelectionTreeNode() { collapse(); @@ -275,7 +266,7 @@ vector<mgSelectionTreeNode*> &mgSelectionTreeNode::getChildren() return m_children; } -// access data in current node +// access data in current node string mgSelectionTreeNode::getID() { return m_id; diff --git a/mg_content_interface.h b/mg_content_interface.h index bb99999..44658e8 100755 --- a/mg_content_interface.h +++ b/mg_content_interface.h @@ -1,13 +1,10 @@ -/*******************************************************************/ /*! \file mg_content_interface.h * \brief data Objects for content (e.g. mp3 files, movies) for the vdr muggle plugin * - ******************************************************************** - * * \version $Revision: 1.4 $ - * \date $Date: 2004/05/28 15:29:18 $ + * \date $Date$ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: lvw $ + * \author file owner: $Author$ * * Declares generic classes of for content items and interfaces to SQL databases * @@ -16,9 +13,7 @@ * - mgContentItem * - mgTracklist * - mgSelectionTreeNode - * */ -/*******************************************************************/ /* makes sure we dont parse the same declarations twice */ #ifndef _CONTENT_INTERFACE_H @@ -115,14 +110,15 @@ class mgContentItem return mgMediaPlayer(); } - /*! \brief get the "file" (or URL) that is passed to the player + //@{ + + /*! \brief return a specific label */ - virtual std::string getSourceFile() + virtual std::string getLabel(int col = 0) { return ""; } - // ============ data access ================= /*! \brief return the title */ virtual std::string getTitle() @@ -130,9 +126,9 @@ class mgContentItem return ""; } - /*! \brief return a specific label + /*! \brief get the "file" (or URL) that is passed to the player */ - virtual std::string getLabel(int col = 0) + virtual std::string getSourceFile() { return ""; } @@ -144,26 +140,62 @@ class mgContentItem return ""; } - virtual std::vector<mgFilter*> *getTrackInfo() + /*! \brief obtain the genre to which the track belongs + */ + virtual std::string getGenre() { - return NULL; + return ""; } - virtual bool updateTrackInfo(std::vector<mgFilter*>*) + /*! \brief obtain the rating of the title + */ + virtual int getRating() { - return false; + return 0; } - virtual std::string getGenre() + /*! \brief obtain the samplerate of the track + */ + virtual int getSampleRate() { - return ""; + return 0; } - virtual int getRating() + /*! \brief obtain the length of the track (in seconds) + */ + virtual int getLength() { return 0; } + /*! \brief obtain the number of audio channels of the track + */ + virtual int getChannels() + { + return 0; + } + + /*! \brief obtain the bitrate of the track + */ + virtual std::string getBitrate() + { + return ""; + } + + /*! \brief obtain track information in aggregated form + */ + virtual std::vector<mgFilter*> *getTrackInfo() + { + return NULL; + } + + //@} + + virtual bool updateTrackInfo(std::vector<mgFilter*>*) + { + return false; + } + virtual bool operator == (mgContentItem o) { return m_uniqID == o.m_uniqID; @@ -206,7 +238,10 @@ class mgTracklist virtual bool remove(unsigned int position); // remove item at position }; - +/*! + * \brief represent a node in a tree of selections + * \ingroup muggle + */ class mgSelectionTreeNode { @@ -1,10 +1,11 @@ /*! \file muggle_tools.h + * \ingroup muggle * \brief A few utility functions for standalone and plugin messaging for the vdr muggle plugindatabase * * \version $Revision: 1.4 $ - * \date $Date: 2004/08/30 14:31:43 $ + * \date $Date$ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: LarsAC $ + * \author file owner: $Author$ * */ @@ -18,18 +19,30 @@ #define STANDALONE 1 // what's this? -// mySql helper functions +/*! \brief mySql helper function to execute read queries + * \ingroup muggle + * + * \todo Could be a member of mgDatabase? + */ MYSQL_RES* mgSqlReadQuery( MYSQL *db, const char *fmt, ... ); + +/*! \brief mySql helper function to execute write queries + * \ingroup muggle + * + * \todo Could be a member of mgDatabase? + */ void mgSqlWriteQuery( MYSQL *db, const char *fmt, ... ); -// Messaging functions +/*! \brief Logging utilities */ +//@{ void mgSetDebugLevel(int new_level); void mgDebug(int level, const char *fmt, ...); void mgDebug( const char *fmt, ... ); void mgWarning(const char *fmt, ...); void mgError(const char *fmt, ...); +//@} #ifdef DEBUG #define MGLOG(x) mgLog __thelog(x) @@ -43,6 +56,13 @@ void mgError(const char *fmt, ...); #define MGLOGSTREAM __thelog.getStream() #endif +/*! \brief simplified logging class + * \ingroup muggle + * + * Create a local instance at the beginning of the method + * and entering/leaving the function will be logged + * as constructors/destructors are called. + */ class mgLog { public: @@ -1,13 +1,33 @@ /*! * \file muggle.h + * \ingroup vdr * \brief Implements a plugin for browsing media libraries within VDR * * \version $Revision: 1.6 $ - * \date $Date: 2004/07/09 12:22:00 $ + * \date $Date$ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author: LarsAC $ + * \author file owner: $Author$ * - * $Id: muggle.h,v 1.6 2004/07/09 12:22:00 LarsAC Exp $ + * $Id$ + */ + + +// Some notes about the general structure of the plugin + +/* \defgroup giantdisc GiantDisc integration layer + * The GiantDisc integration layer contains functions and classes + * which enable interoperability with a database schema that conforms + * to the GiantDisc layout. + * + * \defgroup vdr VDR integration layer + * The VDR integration layer contains components which allow the + * plugin functionality to be accessed by VDR. These are mainly + * related to the OSD and a player/control combination. + * + * \defgroup muggle Main muggle business + * The core of the plugin is an abstract representation of information + * organized in trees (thus suitable for OSD navigation) as well as + * means to organize */ #ifndef _MUGGLE_H @@ -1,340 +1,350 @@ -
-#include <string>
-using namespace std;
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-
-#include <mysql/mysql.h>
-
-#include <taglib/tag.h>
-#include <taglib/fileref.h>
-
-#include "mg_tools.h"
-
-MYSQL *db;
-
-string host, user, pass, dbname;
-bool import_assorted;
-
-int init_database()
-{
- db = mysql_init(NULL);
- if( db == NULL )
- {
- return -1;
- }
-
- if( mysql_real_connect( db, host.c_str(), user.c_str(), pass.c_str(), dbname.c_str(),
- 0, NULL, 0 ) == NULL )
- {
- return -2;
- }
-
- return 0;
-}
-
-time_t get_fs_modification_time( string filename )
-{
- struct stat *buf = (struct stat*) malloc( sizeof( struct stat ) );
-
- // yes: obtain modification date for file and db entry
- int statres = stat( filename.c_str(), buf );
-
- time_t mod = buf->st_mtime;
- free( buf );
-
- return mod;
-}
-
-time_t get_db_modification_time( long uid )
-{
- time_t mt;
-
- MYSQL_RES *result = mgSqlReadQuery( db, "SELECT modification_time FROM tracks WHERE id=\"%d\"", uid );
- MYSQL_ROW row = mysql_fetch_row( result );
-
- string mod_time = row[0];
- mt = (time_t) atol( mod_time.c_str() );
-
- return mt;
-}
-
-TagLib::String escape_string( MYSQL *db, TagLib::String s )
-{
- char *buf = strdup( s.toCString() );
- char *escbuf = (char *) malloc( 2*strlen( buf ) + 1 );
-
- int len = mysql_real_escape_string( db, escbuf, buf, strlen( buf ) );
-
- return TagLib::String( escbuf );
-}
-
-long find_file_in_database( MYSQL *db, string filename )
-{
- TagLib::String file = TagLib::String( filename.c_str() );
- file = escape_string( db, file );
-
- MYSQL_RES *result = mgSqlReadQuery( db, "SELECT id FROM tracks WHERE mp3file=\"%s\"", file.toCString() );
- MYSQL_ROW row = mysql_fetch_row( result );
-
- // obtain ID and return
- return atol( row[0] );
-}
-
-// read tags from the mp3 file and store them into the corresponding database entry
-void update_db( long uid, string filename )
-{
- TagLib::String title, album, artist, genre, cddbid;
- uint trackno, year;
-
- // ID3_Tag filetag( filename.c_str() );
- TagLib::FileRef f( filename.c_str() );
-
- if( !f.isNull() && f.tag() )
- {
- // cout << "Evaluating " << filename << endl;
- TagLib::Tag *tag = f.tag();
-
- // obtain tag information
- title = tag->title();
- album = tag->album();
- year = tag->year();
- artist = tag->artist();
- trackno = tag->track();
- genre = tag->genre();
-
- title = escape_string( db, title );
- album = escape_string( db, album );
- artist = escape_string( db, artist );
-
- // TODO: CD identifier (if it exists), playcounter, popularimeter (rating?), volume adjustment, lyrics, cover
-
- // finally update the database
-
- // obtain associated album or create
- if( album == "" )
- { // no album found, create default album for artist
- MYSQL_RES *result = mgSqlReadQuery( db, "SELECT cddbid FROM album WHERE title=\"Unassigned\" AND artist=\"%s\"", artist.toCString() );
- MYSQL_ROW row = mysql_fetch_row( result );
-
- // Default album does not yet exist (num rows == 0)
- int nrows = mysql_num_rows(result);
- if( nrows == 0 )
- {
- // create new album entry "Unassigned" for this artist
- long id = random();
- char *buf;
- asprintf( &buf, "%d-%s", id, tag->artist().toCString() );
- cddbid = TagLib::String( buf ).substr( 0, 20 );
- cddbid = escape_string( db, cddbid );
- free( buf );
-
- mgSqlWriteQuery( db, "INSERT INTO album (artist,title,cddbid) VALUES (\"%s\", \"Unassigned\", \"%s\")", artist.toCString(), cddbid.toCString() );
- }
- else
- { // use first album found as source id for the track
- cddbid = row[0];
- }
- }
- else
- { // album tag found, associate or create
- MYSQL_RES *result;
- if( import_assorted )
- { // lookup an existing album by title only (artist should be "Various Artists"
- result = mgSqlReadQuery( db, "SELECT cddbid FROM album WHERE title=\"%s\" AND artist=\"Various Artists\"",
- album.toCString(), artist.toCString() );
- }
- else
- {
- result = mgSqlReadQuery( db, "SELECT cddbid FROM album WHERE title=\"%s\" AND artist=\"%s\"",
- album.toCString(), artist.toCString() );
- }
- MYSQL_ROW row = mysql_fetch_row( result );
-
- // num rows == 0 ?
- int nrows = mysql_num_rows(result);
- if( nrows == 0 )
- {
- // create new album entry
- long id = random();
- char *buf;
- asprintf( &buf, "%d-%s", id, tag->album().toCString() );
- cddbid = TagLib::String( buf ).substr( 0, 20 );
- cddbid = escape_string( db, cddbid );
- free( buf );
-
- if( import_assorted )
- { // in this case, the album author is "Various Artists"
- mgSqlWriteQuery( db, "INSERT INTO album (artist,title,cddbid) VALUES (\"Various Artists\", \"%s\", \"%s\")", album.toCString(), cddbid.toCString() ); }
- else
- {
- mgSqlWriteQuery( db, "INSERT INTO album (artist,title,cddbid) VALUES (\"%s\", \"%s\", \"%s\")", artist.toCString(), album.toCString(), cddbid.toCString() ); }
- }
- else
- { // use first album found as source id for the track
- cddbid = row[0];
- }
- }
-
- // update tracks table
- if( uid > 0 )
- { // the entry is known to exist already, hence update it
- mgSqlWriteQuery( db, "UPDATE tracks SET artist=\"%s\", title=\"%s\", year=\"%s\", sourceid=\"%s\", mp3file=\"%s\""
- "WHERE id=%d", artist.toCString(), title.toCString(), year, cddbid.toCString(), filename.c_str(), uid );
- }
- else
- { // the entry does not exist, create it
- // int t = title.find( "'" );
- // int a = artist.find( "'" );
- mgSqlWriteQuery( db,
- "INSERT INTO tracks (artist,title,genre1,genre2,year,sourceid,tracknb,mp3file)"
- " VALUES (\"%s\", \"%s\", \"\", \"\", %d, \"%s\", %d, \"%s\")",
- artist.toCString(), title.toCString(), year, cddbid.toCString(), trackno, filename.c_str() );
- /*
- cout << "-- TAG --" << endl;
- cout << "title - \"" << tag->title() << "\"" << endl;
- cout << "artist - \"" << tag->artist() << "\"" << endl;
- cout << "album - \"" << tag->album() << "\"" << endl;
- cout << "year - \"" << tag->year() << "\"" << endl;
- cout << "comment - \"" << tag->comment() << "\"" << endl;
- cout << "track - \"" << tag->track() << "\"" << endl;
- cout << "genre - \"" << tag->genre() << "\"" << endl;
- */
- }
- }
-}
-
-void update_tags( long uid, string filename )
-{
-
-}
-
-void evaluate_file( MYSQL *db, string filename )
-{
- if( 0 == init_database() )
- {
- // is filename stored in database?
- long uid = find_file_in_database( db, filename );
- if( uid >= 0 )
- {
- // currently only update database, do not consider writing changes from the db back
- /*
- // determine modification times in database and on filesystem
- time_t db_time = get_db_modification_time( uid );
- time_t fs_time = get_fs_modification_time( filename );
-
- if( db_time > fs_time )
- {
- // db is newer: update id3 tags from db
- update_tags( uid, filename );
- }
- else
- {
- // file is newer: update db from id3 tags
- update_db( uid, filename );
- }
- */
-
- update_db( uid, filename );
- }
- else
- {
- // not in db yet: import file
- update_db( -1, filename );
- }
- }
-}
-
-int main( int argc, char *argv[] )
-{
- int option_index;
- string filename;
-
- if( argc < 2 )
- { // we need at least a filename!
- cout << "mugglei -- import helper for Muggle VDR plugin" << endl;
- cout << "(C) Lars von Wedel" << endl;
- cout << "This is free software; see the source for copying conditions." << endl;
- cout << "" << endl;
- cout << "Options:" << endl;
- cout << " -h <hostname> - specify host of mySql database server (default is 'localhost')" << endl;
- cout << " -n <database> - specify database name (default is 'GiantDisc')" << endl;
- cout << " -u <username> - specify user of mySql database (default is empty)" << endl;
- cout << " -p <password> - specify password of user (default is empty password)" << endl;
- cout << " -f <filename> - name of music file to import" << endl;
- cout << " -a - import track as if it was on an assorted album" << endl;
-
- exit( 1 );
- }
-
- // option defaults
- host = "localhost";
- dbname = "GiantDisc";
- user = "";
- pass = "";
- import_assorted = false;
-
- // parse command line options
- while( 1 )
- {
- int c = getopt(argc, argv, "h:u:p:n:af:");
-
- if (c == -1)
- break;
-
- switch (c)
- {
- case 0:
- { // long option
-
- } break;
- case 'h':
- {
- host = optarg;
- } break;
- case 'u':
- {
- user = optarg;
- } break;
- case 'p':
- {
- pass = optarg;
- } break;
- case 'd':
- {
- dbname = optarg;
- } break;
- case 'a':
- {
- import_assorted = true;
- } break;
- case 'f':
- {
- filename = optarg;
- } break;
- }
- }
-
- // init random number generator
- struct timeval tv;
- struct timezone tz;
- gettimeofday( &tv, &tz );
- srandom( tv.tv_usec );
-
- int res = init_database();
-
- if( !res )
- {
- update_db( 0, filename );
- }
- else
- {
- cout << "Database initialization failed. Exiting.\n" << endl;
- }
-
- return res;
-}
-
+ +#include <string> +using namespace std; + +#include <stdlib.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <mysql/mysql.h> + +#include <taglib/tag.h> +#include <taglib/fileref.h> + +#include "mg_tools.h" + +MYSQL *db; + +string host, user, pass, dbname; +bool import_assorted; + +int init_database() +{ + db = mysql_init(NULL); + if( db == NULL ) + { + return -1; + } + + if( mysql_real_connect( db, host.c_str(), user.c_str(), pass.c_str(), dbname.c_str(), + 0, NULL, 0 ) == NULL ) + { + return -2; + } + + return 0; +} + +time_t get_fs_modification_time( string filename ) +{ + struct stat *buf = (struct stat*) malloc( sizeof( struct stat ) ); + + // yes: obtain modification date for file and db entry + int statres = stat( filename.c_str(), buf ); + + time_t mod = buf->st_mtime; + free( buf ); + + return mod; +} + +time_t get_db_modification_time( long uid ) +{ + time_t mt; + + MYSQL_RES *result = mgSqlReadQuery( db, "SELECT modification_time FROM tracks WHERE id=\"%d\"", uid ); + MYSQL_ROW row = mysql_fetch_row( result ); + + string mod_time = row[0]; + mt = (time_t) atol( mod_time.c_str() ); + + return mt; +} + +TagLib::String escape_string( MYSQL *db, TagLib::String s ) +{ + char *buf = strdup( s.toCString() ); + char *escbuf = (char *) malloc( 2*strlen( buf ) + 1 ); + + int len = mysql_real_escape_string( db, escbuf, buf, strlen( buf ) ); + + return TagLib::String( escbuf ); +} + +long find_file_in_database( MYSQL *db, string filename ) +{ + TagLib::String file = TagLib::String( filename.c_str() ); + file = escape_string( db, file ); + + MYSQL_RES *result = mgSqlReadQuery( db, "SELECT id FROM tracks WHERE mp3file=\"%s\"", file.toCString() ); + MYSQL_ROW row = mysql_fetch_row( result ); + + // obtain ID and return + return atol( row[0] ); +} + +// read tags from the mp3 file and store them into the corresponding database entry +void update_db( long uid, string filename ) +{ + TagLib::String title, album, artist, genre, cddbid; + uint trackno, year; + + // ID3_Tag filetag( filename.c_str() ); + TagLib::FileRef f( filename.c_str() ); + + if( !f.isNull() && f.tag() ) + { + // cout << "Evaluating " << filename << endl; + TagLib::Tag *tag = f.tag(); + + // obtain tag information + title = tag->title(); + album = tag->album(); + year = tag->year(); + artist = tag->artist(); + trackno = tag->track(); + genre = tag->genre(); + + AudioProperties *ap = f.audioProperties(); + int len = ap->length(); // tracks.length + int bitrate = ap->bitrate(); // tracks.bitrate + int sample = ap->sampleRate(); //tracks.samplerate + int channels = ap->channels(); //tracks.channels + + title = escape_string( db, title ); + album = escape_string( db, album ); + artist = escape_string( db, artist ); + + // TODO: CD identifier (if it exists), playcounter, popularimeter (rating?), volume adjustment, lyrics, cover + + // finally update the database + + // obtain associated album or create + if( album == "" ) + { // no album found, create default album for artist + MYSQL_RES *result = mgSqlReadQuery( db, "SELECT cddbid FROM album WHERE title=\"Unassigned\" AND artist=\"%s\"", artist.toCString() ); + MYSQL_ROW row = mysql_fetch_row( result ); + + // Default album does not yet exist (num rows == 0) + int nrows = mysql_num_rows(result); + if( nrows == 0 ) + { + // create new album entry "Unassigned" for this artist + long id = random(); + char *buf; + asprintf( &buf, "%d-%s", id, tag->artist().toCString() ); + cddbid = TagLib::String( buf ).substr( 0, 20 ); + cddbid = escape_string( db, cddbid ); + free( buf ); + + mgSqlWriteQuery( db, "INSERT INTO album (artist,title,cddbid) VALUES (\"%s\", \"Unassigned\", \"%s\")", artist.toCString(), cddbid.toCString() ); + } + else + { // use first album found as source id for the track + cddbid = row[0]; + } + } + else + { // album tag found, associate or create + MYSQL_RES *result; + if( import_assorted ) + { // lookup an existing album by title only (artist should be "Various Artists" + result = mgSqlReadQuery( db, "SELECT cddbid FROM album WHERE title=\"%s\" AND artist=\"Various Artists\"", + album.toCString(), artist.toCString() ); + } + else + { + result = mgSqlReadQuery( db, "SELECT cddbid FROM album WHERE title=\"%s\" AND artist=\"%s\"", + album.toCString(), artist.toCString() ); + } + MYSQL_ROW row = mysql_fetch_row( result ); + + // num rows == 0 ? + int nrows = mysql_num_rows(result); + if( nrows == 0 ) + { + // create new album entry + long id = random(); + char *buf; + asprintf( &buf, "%d-%s", id, tag->album().toCString() ); + cddbid = TagLib::String( buf ).substr( 0, 20 ); + cddbid = escape_string( db, cddbid ); + free( buf ); + + if( import_assorted ) + { // in this case, the album author is "Various Artists" + mgSqlWriteQuery( db, "INSERT INTO album (artist,title,cddbid) VALUES (\"Various Artists\", \"%s\", \"%s\")", album.toCString(), cddbid.toCString() ); } + else + { + mgSqlWriteQuery( db, "INSERT INTO album (artist,title,cddbid) VALUES (\"%s\", \"%s\", \"%s\")", artist.toCString(), album.toCString(), cddbid.toCString() ); } + } + else + { // use first album found as source id for the track + cddbid = row[0]; + } + } + + // update tracks table + if( uid > 0 ) + { // the entry is known to exist already, hence update it + + mgSqlWriteQuery( db, "UPDATE tracks SET artist=\"%s\", title=\"%s\", year=\"%s\"," + "sourceid=\"%s\", mp3file=\"%s\", length=%d, bitrate=\"%d\"," + "samplerate=%d, channels=%d WHERE id=%d", + artist.toCString(), title.toCString(), year, + cddbid.toCString(), filename.c_str(), len, bitrate, + sample, channels, uid ); + } + else + { // the entry does not exist, create it + mgSqlWriteQuery( db,"INSERT INTO tracks (artist,title,genre1,genre2,year," + "sourceid,tracknb,mp3file,length,bitrate,samplerate,channels)" + " VALUES (\"%s\", \"%s\", \"\", \"\", %d, \"%s\", %d, \"%s\", %d, \"%d\", %d, %d)", + artist.toCString(), title.toCString(), year, cddbid.toCString(), + trackno, filename.c_str(), len, bitrate, sample, channels ); + /* + cout << "-- TAG --" << endl; + cout << "title - \"" << tag->title() << "\"" << endl; + cout << "artist - \"" << tag->artist() << "\"" << endl; + cout << "album - \"" << tag->album() << "\"" << endl; + cout << "year - \"" << tag->year() << "\"" << endl; + cout << "comment - \"" << tag->comment() << "\"" << endl; + cout << "track - \"" << tag->track() << "\"" << endl; + cout << "genre - \"" << tag->genre() << "\"" << endl; + */ + } + } +} + +void update_tags( long uid, string filename ) +{ + +} + +void evaluate_file( MYSQL *db, string filename ) +{ + if( 0 == init_database() ) + { + // is filename stored in database? + long uid = find_file_in_database( db, filename ); + if( uid >= 0 ) + { + // currently only update database, do not consider writing changes from the db back + /* + // determine modification times in database and on filesystem + time_t db_time = get_db_modification_time( uid ); + time_t fs_time = get_fs_modification_time( filename ); + + if( db_time > fs_time ) + { + // db is newer: update id3 tags from db + update_tags( uid, filename ); + } + else + { + // file is newer: update db from id3 tags + update_db( uid, filename ); + } + */ + + update_db( uid, filename ); + } + else + { + // not in db yet: import file + update_db( -1, filename ); + } + } +} + +int main( int argc, char *argv[] ) +{ + int option_index; + string filename; + + if( argc < 2 ) + { // we need at least a filename! + cout << "mugglei -- import helper for Muggle VDR plugin" << endl; + cout << "(C) Lars von Wedel" << endl; + cout << "This is free software; see the source for copying conditions." << endl; + cout << "" << endl; + cout << "Options:" << endl; + cout << " -h <hostname> - specify host of mySql database server (default is 'localhost')" << endl; + cout << " -n <database> - specify database name (default is 'GiantDisc')" << endl; + cout << " -u <username> - specify user of mySql database (default is empty)" << endl; + cout << " -p <password> - specify password of user (default is empty password)" << endl; + cout << " -f <filename> - name of music file to import" << endl; + cout << " -a - import track as if it was on an assorted album" << endl; + + exit( 1 ); + } + + // option defaults + host = "localhost"; + dbname = "GiantDisc"; + user = ""; + pass = ""; + import_assorted = false; + + // parse command line options + while( 1 ) + { + int c = getopt(argc, argv, "h:u:p:n:af:"); + + if (c == -1) + break; + + switch (c) + { + case 0: + { // long option + + } break; + case 'h': + { + host = optarg; + } break; + case 'u': + { + user = optarg; + } break; + case 'p': + { + pass = optarg; + } break; + case 'd': + { + dbname = optarg; + } break; + case 'a': + { + import_assorted = true; + } break; + case 'f': + { + filename = optarg; + } break; + } + } + + // init random number generator + struct timeval tv; + struct timezone tz; + gettimeofday( &tv, &tz ); + srandom( tv.tv_usec ); + + int res = init_database(); + + if( !res ) + { + update_db( 0, filename ); + } + else + { + cout << "Database initialization failed. Exiting.\n" << endl; + } + + return res; +} + diff --git a/scripts/createtables.mysql b/scripts/createtables.mysql index 5969ce2..079b3bd 100755 --- a/scripts/createtables.mysql +++ b/scripts/createtables.mysql @@ -219,6 +219,10 @@ CREATE TABLE tracks ( created date default NULL, modified date default NULL, backup tinyint(3) unsigned default NULL, + + samplerate int(7) unsigned default NULL, + channels tinyint(3) unsigned default NULL, + id int(11) NOT NULL auto_increment, PRIMARY KEY (id), KEY title (title(10)), diff --git a/vdr_decoder.c b/vdr_decoder.c index 8b4cf70..1c0b154 100644 --- a/vdr_decoder.c +++ b/vdr_decoder.c @@ -1,13 +1,14 @@ /*! * \file vdr_decoder.h * \brief A generic decoder for a VDR media plugin (muggle) + * \ingroup vdr * * \version $Revision: 1.2 $ - * \date $Date: 2004/05/28 15:29:18 $ + * \date $Date$ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author: lvw $ + * \author Responsible author: $Author$ * - * $Id: vdr_decoder.c,v 1.2 2004/05/28 15:29:18 lvw Exp $ + * $Id$ * * Adapted from: * MP3/MPlayer plugin to VDR (C++) @@ -23,15 +24,11 @@ #include <videodir.h> #include <interface.h> -// #include "common.h" -// #include "data-mp3.h" -// #include "decoder-core.h" -// #include "decoder-mp3-stream.h" -// #include "decoder-snd.h" -// #include "decoder-ogg.h" - #include "vdr_decoder.h" #include "vdr_decoder_mp3.h" +#include "vdr_decoder_ogg.h" + +#include "mg_content_interface.h" using namespace std; @@ -39,38 +36,58 @@ using namespace std; mgMediaType mgDecoders::getMediaType( string s ) { + mgMediaType mt = MT_UNKNOWN; + // TODO: currently handles only mp3. LVW - return MT_MP3; + char *f = (char *)s.c_str(); + char *p = f + strlen( f ) - 1; // point to the end + + while( p >= f && *p != '.') --p; + + if( !strcmp( p, ".mp3" ) ) + { + mt = MT_MP3; + } + else + { + if( !strcmp( p, ".ogg" ) ) + { + mt = MT_OGG; + } + } + return mt; } -mgDecoder *mgDecoders::findDecoder( string filename ) +mgDecoder *mgDecoders::findDecoder( mgContentItem *item ) { mgDecoder *decoder = 0; + string filename = item->getSourceFile(); + switch( getMediaType( filename ) ) { case MT_MP3: { - // prepend filename with path to music library?? TODO - printf( "mp3 file detected, launching mgMP3Decoder\n" ); - decoder = new mgMP3Decoder(filename); + decoder = new mgMP3Decoder( item ); } break; - - /* - case MT_MP3_STREAM: decoder = new mgMP3StreamDecoder(full); break; - #ifdef HAVE_SNDFILE - case MT_SND: decoder = new cSndDecoder(full); break; - #endif - #ifdef HAVE_VORBISFILE - case MT_OGG: decoder = new mgOggDecoder(full); break; - #endif - */ +#ifdef HAVE_VORBISFILE + case MT_OGG: + { + decoder = new mgOggDecoder( item ); + } break; +#endif + /* + case MT_MP3_STREAM: decoder = new mgMP3StreamDecoder(full); break; + #ifdef HAVE_SNDFILE + case MT_SND: decoder = new cSndDecoder(full); break; + #endif + */ default: { esyslog("ERROR: unknown media type" ); } break; } - + if( decoder && !decoder->valid() ) { // no decoder found or decoder doesn't match @@ -85,9 +102,9 @@ mgDecoder *mgDecoders::findDecoder( string filename ) // --- mgDecoder ---------------------------------------------------------------- -mgDecoder::mgDecoder(string filename) +mgDecoder::mgDecoder( mgContentItem *item ) { - m_filename = filename; + m_item = item; m_locked = 0; m_urgentLock = false; m_playing = false; diff --git a/vdr_decoder.h b/vdr_decoder.h index 74b200e..a673cbc 100644 --- a/vdr_decoder.h +++ b/vdr_decoder.h @@ -1,13 +1,14 @@ /*! * \file vdr_decoder.h * \brief A generic decoder for a VDR media plugin (muggle) + * \ingroup vdr * * \version $Revision: 1.2 $ - * \date $Date: 2004/05/28 15:29:18 $ + * \date $Date$ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author: lvw $ + * \author Responsible author: $Author$ * - * $Id: vdr_decoder.h,v 1.2 2004/05/28 15:29:18 lvw Exp $ + * $Id$ * * Adapted from * MP3/MPlayer plugin to VDR (C++) @@ -24,10 +25,13 @@ #define DEC_ID(a,b,c,d) (((a)<<24)+((b)<<16)+((c)<<8)+(d)) +class mgContentItem; + // --------From decoder_core.h ------------------------------------ /*! * \brief The current status of the decoder + * \ingroup vdr */ enum eDecodeStatus { @@ -38,6 +42,7 @@ enum eDecodeStatus /*! * \brief A data structure to put decoded PCM data + * \ingroup vdr */ struct mgDecode { @@ -50,6 +55,7 @@ struct mgDecode /*! * \brief Information about ??? + * \ingroup vdr */ class mgPlayInfo { @@ -61,19 +67,24 @@ public: /*! * \brief Media types + * \ingroup vdr */ enum mgMediaType { - MT_MP3, MT_MP3_STREAM, MT_OGG, MT_FLAC + MT_MP3, MT_MP3_STREAM, MT_OGG, MT_FLAC, MT_UNKNOWN }; /*! * \brief A generic decoder class to handle conversion into PCM format + * \ingroup vdr */ class mgDecoder { protected: + /*! \brief database handle to the track being decoded */ + mgContentItem *m_item; + /*! \brief The currently playing file */ std::string m_filename; @@ -99,11 +110,13 @@ protected: public: + //@{ /*! \brief The constructor */ - mgDecoder( std::string filename ); + mgDecoder( mgContentItem *item ); /*! \brief The destructor */ virtual ~mgDecoder(); + //@} /*! \brief Whether a decoder instance is able to play the given file */ virtual bool valid() = 0; @@ -147,7 +160,7 @@ public: /*! \brief Try to find a valid decoder for a file */ - static mgDecoder *findDecoder( std::string filename ); + static mgDecoder *findDecoder( mgContentItem *item ); /*! \brief determine the media type for a given source */ diff --git a/vdr_decoder_mp3.c b/vdr_decoder_mp3.c index 436d7b6..20fca5e 100644 --- a/vdr_decoder_mp3.c +++ b/vdr_decoder_mp3.c @@ -3,11 +3,11 @@ * \brief An mp3 decoder for a VDR media plugin (muggle) * * \version $Revision: 1.2 $ - * \date $Date: 2004/05/28 15:29:19 $ + * \date $Date$ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author: lvw $ + * \author Responsible author: $Author$ * - * $Id: vdr_decoder_mp3.c,v 1.2 2004/05/28 15:29:19 lvw Exp $ + * $Id$ * * Adapted from * MP3/MPlayer plugin to VDR (C++) @@ -27,6 +27,7 @@ #include "vdr_decoder_mp3.h" #include "vdr_stream.h" +#include "mg_content_interface.h" #include "mg_tools.h" #define d(x) x @@ -52,17 +53,16 @@ int mgMadStream(struct mad_stream *stream, mgStream *str) // --- mgMP3Decoder ------------------------------------------------------------- -mgMP3Decoder::mgMP3Decoder(string filename, bool preinit) - : mgDecoder(filename) +mgMP3Decoder::mgMP3Decoder( mgContentItem *item, bool preinit) + : mgDecoder(item) { - MGLOG( "mgMP3Decoder::mgMP3Decoder" ); - m_stream = 0; m_isStream = false; + m_filename = item->getSourceFile(); if( preinit ) { - m_stream = new mgStream( filename ); + m_stream = new mgStream( m_filename ); } m_madframe = 0; m_madsynth = 0; @@ -181,8 +181,6 @@ mgPlayInfo *mgMP3Decoder::playInfo() bool mgMP3Decoder::start() { - MGLOG( "mgMP3Decoder::start" ); - lock(true); init(); m_playing = true; @@ -219,8 +217,6 @@ bool mgMP3Decoder::start() bool mgMP3Decoder::stop(void) { - MGLOG( "mgMP3Decoder::stop" ); - lock(); if( m_playing ) @@ -234,8 +230,6 @@ bool mgMP3Decoder::stop(void) struct mgDecode *mgMP3Decoder::done(eDecodeStatus status) { - // MGLOG( "mgMP3Decoder::done" ); - m_ds.status = status; m_ds.index = mad_timer_count( m_playtime, MAD_UNITS_MILLISECONDS ); m_ds.pcm = &m_madsynth->pcm; diff --git a/vdr_decoder_mp3.h b/vdr_decoder_mp3.h index abc8eed..f9f3bde 100644 --- a/vdr_decoder_mp3.h +++ b/vdr_decoder_mp3.h @@ -3,11 +3,11 @@ * \brief An mp3 decoder for a VDR media plugin (muggle) * * \version $Revision: 1.2 $ - * \date $Date: 2004/05/28 15:29:19 $ + * \date $Date$ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author: lvw $ + * \author Responsible author: $Author$ * - * $Id: vdr_decoder_mp3.h,v 1.2 2004/05/28 15:29:19 lvw Exp $ + * $Id$ * * Adapted from * MP3/MPlayer plugin to VDR (C++) @@ -30,6 +30,7 @@ #endif class mgStream; +class mgContentItem; // ---------------------------------------------------------------- @@ -55,7 +56,7 @@ private: } *m_frameinfo; int m_framenum, m_framemax, m_errcount, m_mute; - // + void init(); void clean(); @@ -65,7 +66,7 @@ private: virtual mgPlayInfo *playInfo(); eDecodeStatus decodeError(bool hdr); - + void makeSkipTime(mad_timer_t *skiptime, mad_timer_t playtime, int secs, int avail, int dvbrate); @@ -78,7 +79,7 @@ public: /*! * \brief construct a decoder from a filename */ - mgMP3Decoder( std::string filename, bool preinit = true ); + mgMP3Decoder( mgContentItem *item, bool preinit = true ); /*! * \brief the destructor diff --git a/vdr_decoder_ogg.c b/vdr_decoder_ogg.c new file mode 100644 index 0000000..9525979 --- /dev/null +++ b/vdr_decoder_ogg.c @@ -0,0 +1,393 @@ +/*! \file vdr_decoder_ogg.c + * \ingroup vdr + * + * The file implements a decoder which is used by the player to decode ogg vorbis audio files. + * + * Adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001-2003 Stefan Huelswitt <huels@iname.com> + */ + + +#ifdef HAVE_VORBISFILE + +#include "vdr_decoder_ogg.h" + +#include <mad.h> +#include <vorbis/vorbisfile.h> + +#include <string> +#include <stdlib.h> +#include <stdio.h> + +#include "mg_content_interface.h" + +using namespace std; + +// --- mgOggFile ---------------------------------------------------------------- + +class mgOggFile // : public mgFileInfo +{ + private: + + bool m_opened, m_canSeek; + + OggVorbis_File vf; + + void error( const char *action, const int err ); + + string m_filename; + + public: + + mgOggFile( string filename ); + ~mgOggFile(); + + bool open(bool log=true); + + void close(void); + + long long seek(long long posMs=0, bool relativ=false); + + int stream(short *buffer, int samples); + + bool canSeek() { return m_canSeek; } + + long long indexMs(void); +}; + +mgOggFile::mgOggFile( string filename ) : + m_filename( filename ) +{ + m_canSeek = false; + m_opened = false; +} + +mgOggFile::~mgOggFile() +{ + close(); +} + +bool mgOggFile::open(bool log) +{ + if( m_opened ) + { + if( m_canSeek ) + { + return ( seek() >= 0 ); + } + return true; + } + + FILE *f = fopen( m_filename.c_str(), "r" ); + if( f ) + { + int r = ov_open( f, &vf, 0, 0 ); + if( !r ) + { + m_canSeek = ( ov_seekable( &vf ) !=0 ); + m_opened = true; + } + else + { + fclose( f ); + if( log ) + { + error( "open", r ); + } + } + } + else + { + if(log) + { + // esyslog("ERROR: failed to open file %s: %s", m_filename.c_str(), strerror(errno) ); + } + } + return m_opened; +} + +void mgOggFile::close() +{ + if( m_opened ) + { + ov_clear( &vf ); + m_opened = false; + } +} + +void mgOggFile::error( const char *action, const int err ) +{ + char *errstr; + switch(err) + { + case OV_FALSE: errstr = "false/no data available"; break; + case OV_EOF: errstr = "EOF"; break; + case OV_HOLE: errstr = "missing or corrupted data"; break; + case OV_EREAD: errstr = "read error"; break; + case OV_EFAULT: errstr = "internal error"; break; + case OV_EIMPL: errstr = "unimplemented feature"; break; + case OV_EINVAL: errstr = "invalid argument"; break; + case OV_ENOTVORBIS: errstr = "no Ogg Vorbis stream"; break; + case OV_EBADHEADER: errstr = "corrupted Ogg Vorbis stream"; break; + case OV_EVERSION: errstr = "unsupported bitstream version"; break; + case OV_ENOTAUDIO: errstr = "ENOTAUDIO"; break; + case OV_EBADPACKET: errstr = "EBADPACKET"; break; + case OV_EBADLINK: errstr = "corrupted link"; break; + case OV_ENOSEEK: errstr = "stream not seekable"; break; + default: errstr = "unspecified error"; break; + } + // esyslog( "ERROR: vorbisfile %s failed on %s: %s", action, m_filename.c_str(), errstr ); +} + +long long mgOggFile::indexMs(void) +{ + double p = ov_time_tell(&vf); + if( p < 0.0 ) + { + p = 0.0; + } + + return (long long)( p*1000.0 ); +} + +long long mgOggFile::seek( long long posMs, bool relativ ) +{ + if( relativ ) + { + posMs += indexMs(); + } + + int r = ov_time_seek( &vf, (double) posMs/1000.0 ); + + if(r) + { + error( "seek", r ); + return -1; + } + + posMs = indexMs(); + return posMs; +} + +int mgOggFile::stream( short *buffer, int samples ) +{ + int n; + do + { + int stream; + n = ov_read( &vf, (char *)buffer, samples*2, 0, 2, 1, &stream ); + } + while( n == OV_HOLE ); + + if(n < 0) + { + error( "read", n ); + } + + return (n/2); +} + +// --- mgOggDecoder ------------------------------------------------------------- + +mgOggDecoder::mgOggDecoder( mgContentItem *item ) + : mgDecoder( item ) +{ + m_filename = item->getSourceFile(); + m_file = new mgOggFile( m_filename ); + m_pcm = 0; + init(); +} + +mgOggDecoder::~mgOggDecoder() +{ + delete m_file; + clean(); +} + +bool mgOggDecoder::valid() +{ + bool res = false; + if( tryLock() ) + { + if( m_file->open( false ) ) + { + res = true; + } + unlock(); + } + return res; +} + +mgPlayInfo *mgOggDecoder::playInfo(void) +{ + if( m_playing ) + { + // m_playinfo.m_index = index/1000; + // m_playinfo.m_total = info.Total; + + return &m_playinfo; + } + + return 0; +} + +void mgOggDecoder::init() +{ + clean(); + m_pcm = new struct mad_pcm; + m_index = 0; +} + +bool mgOggDecoder::clean() +{ + m_playing = false; + + delete m_pcm; + m_pcm = 0; + + m_file->close(); + return false; +} + +#define SF_SAMPLES (sizeof(m_pcm->samples[0])/sizeof(mad_fixed_t)) + +bool mgOggDecoder::start() +{ + lock(true); + init(); + m_playing = true; + + if( m_file->open() /*&& info.DoScan(true)*/ ) + { + // obtain from database: rate, channels + /* d(printf("ogg: open rate=%d channels=%d seek=%d\n", + info.SampleFreq,info.Channels,file.CanSeek())) + */ + if( m_item->getChannels() <= 2 ) + { + unlock(); + return true; + } + else + { + // esyslog( "ERROR: cannot play ogg file %s: more than 2 channels", m_filename.c_str() ); + } + } + + clean(); + unlock(); + + return false; +} + +bool mgOggDecoder::stop(void) +{ + lock(); + + if( m_playing ) + { + clean(); + } + unlock(); + + return true; +} + +struct mgDecode *mgOggDecoder::done(eDecodeStatus status) +{ + m_ds.status = status; + m_ds.index = m_index; + m_ds.pcm = m_pcm; + + unlock(); // release the lock from decode() + + return &m_ds; +} + +struct mgDecode *mgOggDecoder::decode(void) +{ + lock(); // this is released in Done() + + if( m_playing ) + { + short framebuff[2*SF_SAMPLES]; + int n = m_file->stream( framebuff, SF_SAMPLES ); + + if( n < 0 ) + { + return done(dsError); + } + + if( n == 0 ) + { + return done(dsEof); + } + + // should be done during initialization + m_pcm->samplerate = m_item->getSampleRate(); // from database + m_pcm->channels = m_item->getChannels(); // from database + + n /= m_pcm->channels; + m_pcm->length = n; + m_index = m_file->indexMs(); + + short *data = framebuff; + mad_fixed_t *sam0 = m_pcm->samples[0], *sam1 = m_pcm->samples[1]; + + const int s = MAD_F_FRACBITS + 1 - ( sizeof(short)*8 ); // shift value for mad_fixed conversion + + if( m_pcm->channels>1 ) + { + for(; n > 0 ; n-- ) + { + *sam0++=(*data++) << s; + *sam1++=(*data++) << s; + } + } + else + { + for(; n>0 ; n--) + { + *sam0++=(*data++) << s; + } + } + return done(dsPlay); + } + return done(dsError); +} + +bool mgOggDecoder::skip(int Seconds, int Avail, int Rate) +{ + lock(); + bool res = false; + + if( m_playing && m_file->canSeek() ) + { + float fsecs = (float)Seconds - ( (float)Avail / (float)(Rate * (16/8 * 2) ) ); + // Byte/s = samplerate * 16 bit * 2 chan + + long long newpos = m_file->indexMs() + (long long)(fsecs*1000.0); + + if( newpos < 0 ) + { + newpos=0; + } + + newpos = m_file->seek( newpos, false ); + + if( newpos >= 0 ) + { + m_index = m_file->indexMs(); +#ifdef DEBUG + int i = index/1000; + printf( "ogg: skipping to %02d:%02d\n", i/60, i%60 ); +#endif + res = true; + } + } + unlock(); + return res; +} + +#endif //HAVE_VORBISFILE diff --git a/vdr_decoder_ogg.h b/vdr_decoder_ogg.h new file mode 100644 index 0000000..b6bfc17 --- /dev/null +++ b/vdr_decoder_ogg.h @@ -0,0 +1,60 @@ +/*! \file vdr_decoder_ogg.h + * \ingroup vdr + * + * The file contains a decoder which is used by the player to decode ogg vorbis audio files. + * + * Adapted from + * MP3/MPlayer plugin to VDR (C++) + * (C) 2001-2003 Stefan Huelswitt <huels@iname.com> + */ + +#ifndef ___DECODER_OGG_H +#define ___DECODER_OGG_H + +#define DEC_OGG DEC_ID('O','G','G',' ') +#define DEC_OGG_STR "OGG" + +#ifdef HAVE_VORBISFILE + +#include "vdr_decoder.h" + +// ---------------------------------------------------------------- + +class mgOggFile; + +class mgOggDecoder : public mgDecoder +{ + private: + + mgOggFile *m_file; + struct mgDecode m_ds; + struct mad_pcm *m_pcm; + unsigned long long m_index; + + // + void init(void); + bool clean(void); + struct mgDecode *done( eDecodeStatus status ); + + public: + + mgOggDecoder( mgContentItem *item ); + ~mgOggDecoder(); + + virtual mgPlayInfo *playInfo(); + + virtual bool valid(); + + virtual bool start(); + + virtual bool stop(); + + virtual bool skip(int Seconds, int Avail, int Rate); + + virtual struct mgDecode *decode(void); +}; + +// ---------------------------------------------------------------- + +#endif //HAVE_VORBISFILE +#endif //___DECODER_OGG_H diff --git a/vdr_player.c b/vdr_player.c index 105072b..5cde3fe 100644 --- a/vdr_player.c +++ b/vdr_player.c @@ -3,11 +3,11 @@ * \brief A generic PCM player for a VDR media plugin (muggle) * * \version $Revision: 1.7 $ - * \date $Date: 2004/07/27 20:50:54 $ + * \date $Date$ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author: lvw $ + * \author Responsible author: $Author$ * - * $Id: vdr_player.c,v 1.7 2004/07/27 20:50:54 lvw Exp $ + * $Id$ * * Adapted from * MP3/MPlayer plugin to VDR (C++) @@ -176,7 +176,6 @@ private: void SetPlayMode(ePlayMode mode); void WaitPlayMode(ePlayMode mode, bool inv); - string getSourceFile(); protected: virtual void Activate(bool On); virtual void Action(void); @@ -363,10 +362,10 @@ void mgPCMPlayer::Action(void) m_index = 0; m_playing = m_current; - string filename = getSourceFile(); + string filename = m_playing->getSourceFile(); mgDebug( 1, "mgPCMPlayer::Action: music file is %s", filename.c_str() ); - if( ( m_decoder = mgDecoders::findDecoder( filename ) ) && m_decoder->start() ) + if( ( m_decoder = mgDecoders::findDecoder( m_playing ) ) && m_decoder->start() ) { levelgood = true; haslevel = false; @@ -671,38 +670,6 @@ void mgPCMPlayer::StopPlay() } } -string mgPCMPlayer::getSourceFile() -{ - string filename; - - if( !the_setup.GdCompatibility ) - { // use filename itself - filename = string( the_setup.ToplevelDir ) + m_playing->getSourceFile(); - } - else - { // find the unique name within any directory, but what is top? video? - char *cmd = NULL; - asprintf( &cmd, "find %s -follow -type f -name '%s'|sort ", - the_setup.ToplevelDir, m_playing->getSourceFile().c_str() ); - - FILE *p = popen(cmd, "r"); - if (p) - { - char *s; - if( (s = readline(p) ) != NULL ) - { - filename = string( s ); - } - } - pclose( p ); - delete cmd; - } - - cout << "mgPCMPlayer::getSourceFile: found filename " << filename << endl << flush; - return filename; - // return "/test.mp3"; -} - bool mgPCMPlayer::NextFile() { bool res = false; |