diff options
-rw-r--r-- | Makefile | 11 | ||||
-rw-r--r-- | README | 162 | ||||
-rw-r--r-- | TODO | 125 | ||||
-rw-r--r-- | gd_content_interface.c | 1425 | ||||
-rw-r--r-- | gd_content_interface.h | 582 | ||||
-rw-r--r-- | i18n.c | 1961 | ||||
-rw-r--r-- | i18n.h | 3 | ||||
-rw-r--r-- | mg_actions.c | 986 | ||||
-rw-r--r-- | mg_actions.h | 168 | ||||
-rwxr-xr-x | mg_content_interface.c | 241 | ||||
-rwxr-xr-x | mg_content_interface.h | 480 | ||||
-rw-r--r-- | mg_database.c | 1 | ||||
-rw-r--r-- | mg_db.c | 1517 | ||||
-rw-r--r-- | mg_db.h | 1071 | ||||
-rw-r--r-- | mg_media.c | 453 | ||||
-rw-r--r-- | mg_media.h | 209 | ||||
-rw-r--r-- | mg_playlist.c | 368 | ||||
-rw-r--r-- | mg_playlist.h | 209 | ||||
-rw-r--r-- | mg_tools.c | 154 | ||||
-rw-r--r-- | mg_tools.h | 85 | ||||
-rw-r--r-- | muggle.c | 342 | ||||
-rw-r--r-- | muggle.doxygen | 2 | ||||
-rw-r--r-- | muggle.h | 53 | ||||
-rwxr-xr-x | mugglei.c | 65 | ||||
-rwxr-xr-x | scripts/createdb.mysql | 10 | ||||
-rwxr-xr-x | scripts/createtables.mysql | 2 | ||||
-rw-r--r-- | vdr_config.h | 11 | ||||
-rw-r--r-- | vdr_decoder.c | 169 | ||||
-rw-r--r-- | vdr_decoder.h | 153 | ||||
-rw-r--r-- | vdr_decoder_mp3.c | 608 | ||||
-rw-r--r-- | vdr_decoder_mp3.h | 115 | ||||
-rw-r--r-- | vdr_decoder_ogg.c | 599 | ||||
-rw-r--r-- | vdr_decoder_ogg.h | 49 | ||||
-rw-r--r-- | vdr_menu.c | 1390 | ||||
-rw-r--r-- | vdr_menu.h | 386 | ||||
-rw-r--r-- | vdr_network.h | 54 | ||||
-rw-r--r-- | vdr_player.c | 2632 | ||||
-rw-r--r-- | vdr_player.h | 167 | ||||
-rw-r--r-- | vdr_setup.c | 97 | ||||
-rw-r--r-- | vdr_setup.h | 66 | ||||
-rw-r--r-- | vdr_sound.c | 935 | ||||
-rw-r--r-- | vdr_stream.c | 488 | ||||
-rw-r--r-- | vdr_stream.h | 65 |
43 files changed, 9508 insertions, 9161 deletions
@@ -16,7 +16,7 @@ VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).c | awk '{ pri ### The C++ compiler and options: CXX ?= g++-3.3 -CXXFLAGS ?= -fPIC -O2 -Wall -Woverloaded-virtual -Wno-deprecated -g +CXXFLAGS ?= -fPIC -O0 -Wall -Woverloaded-virtual -Wno-deprecated -g ### The directory environment: @@ -51,14 +51,16 @@ DEFINES += -D_GNU_SOURCE MIFLAGS += -I/usr/include/taglib -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 vdr_decoder_ogg.o +OBJS = $(PLUGIN).o i18n.o mg_db.o mg_actions.o vdr_menu.o mg_tools.o \ + vdr_decoder_mp3.o vdr_stream.o vdr_decoder.o vdr_player.o \ + vdr_setup.o vdr_decoder_ogg.o LIBS = -lmad -lmysqlclient -lvorbisfile -lvorbis MILIBS = -lmysqlclient -ltag ### Targets: -all: libvdr-$(PLUGIN).so mugglei +all: libvdr-$(PLUGIN).so # Dependencies: @@ -80,11 +82,10 @@ libvdr-$(PLUGIN).so: $(OBJS) mugglei: mg_tools.o mugglei.o $(CXX) $(CXXFLAGS) $^ $(MILIBS) -o $@ - install: @cp ../../lib/libvdr-muggle*.so.* /usr/lib/vdr/ @cp mugglei /usr/local/bin/ - @install -m 755 mugglei /usr/local/bin/ +# @install -m 755 mugglei /usr/local/bin/ dist: clean @-rm -rf $(TMPDIR)/$(ARCHIVE) @@ -2,13 +2,14 @@ This is a "plugin" for the Video Disk Recorder (VDR). -Written by: Andi Kellner - Lars von Wedel <vonwedel@web.de> +Written by: Andi Kellner, + Lars von Wedel <vonwedel@web.de>, Ralf Klueber <r@lf-klueber.de>, + Wolfgang Rohdewald <wolfgang@rohdewald.de> Project's homepage: http://www.htpc-tech.de/htpc/muggle.htm -Latest version available at: http://www.htpc-tech.de/htpc/muggle_archive/vdr-muggle-0.0.8-BETA.tgz +Latest version available at: http://www.htpc-tech.de/htpc/muggle-dev.htm See the file COPYING for license information. @@ -24,16 +25,6 @@ Please provide feedback to the authors whenever you think, these instructions are not appropriate, wrong, or useless in any other sense. -\section ack Acknowledgements - -Thanks to all who have supported the development of this plugin. Special thanks (order does not mean importance :-) go to -- Muempf for the mp3 plugin. All code related to audio replay is largely taken over from this plugin. -- LordJaxom for constant support in the chat of VDR portal regarding OSD programming in VDR -- eloy (member of vdrportal.de) for alpha testing -- All beta testers at vdrportal.de and on the VDR mailing list -- decembersoul (member of vdrportal.de) for finding out how to run muggle on LinVDR -- Hulk (member of vdrportal.de) for submitting several patches (especially for gLCD display) - \section desc DESCRIPTION The muggle plugin provides a database link for VDR so that selection of media becomes more flexible. @@ -43,7 +34,7 @@ parameters are descibed in Section 5. \section prereq PREREQUISITES -The plugin has been tested with VDR versions up to 1.3.12. In addition, the following pieces of +The plugin is written for VDR 1.2.6. In addition, the following pieces of software are required: - mySQL server (tested with 4.0.18) (Debian packages mysql-server, mysql-client) @@ -63,8 +54,7 @@ software are required: The developer versions are needed because their headers are required for compilation. The server need not be on the same machine as the VDR. Also, music tracks can reside somewhere else, if they are available through a remote filesystem (NFS, Samba). However, in this case you should -know what you are doing in terms of networking and security issues. In my personal setup, the mySQL -database runs on a server where also all music files are stored. Muggle accesses them via Samba. +know what you are doing in terms of networking and security issues. \section install INSTALLING @@ -82,7 +72,7 @@ Establish a symlink as you would for other plugins: ln -s muggle-0.1.7 muggle \endverbatim -Note that the actual directory names may vary, e.g. the version number will changes. Within the VDR main directory (e.g. /usr/local/src/VDR) issue a +Within the VDR main directory (e.g. /usr/local/src/VDR) issue \verbatim make plugins @@ -93,13 +83,14 @@ in the library directories stated in the muggle Makefile. \section import IMPORT -The import is done in two steps: First, a database is created and initialized with proper data structures (so-called schema). Then, these data structures are filled from the ID3 tags of your music tracks. +The import is done in two steps: First, a database is created and initialized with proper data structures (so-called schema). +Then, these data structures are filled from the ID3 tags of your music tracks. \subsection dbsetup Setup Database This step can be done on the database server or on some other client machine. Within the directory scripts there are a few helpful files to support setting -up the database. Change into that directory: +up the database. Change into that directory:# \verbatim cd scripts @@ -135,7 +126,7 @@ Execute these commands on a single line, the \ for the linebreak ist just for pr mysql -u root --local-infile=1 \endverbatim -You can find the sequence of commands in the file scripts/make-empty-db. Use it at your own luck. +You can find the sequence of commands in the file scripts/make-empty-db. Use it at your own luck after making necessary modification (program paths, database names, servers, users, etc.). Please note, that the scripts and commands above are quite basic in terms of security (e.g. no password set for the vdr user, no proper selection of privileges). You may want to spend some @@ -159,14 +150,14 @@ It does not matter whether there are further subdirectories which organize files album or whatever. If this is not the case, you may want to take some time to do this. Read on before you start -You probably do not want to import all files in one go: albums on which tracks of various artists are -found (samplers) require different treatment than albums containing files of just one artist. What I did: -all samplers are collected below a special subdirectory "Assorted". Import is then run separately for those -tracks. +You probably do not want to import all files in one go: albums on which tracks of various artists are found +(samplers) require different treatment than files of just one artist. What I did: all samplers are collected +below a special subdirectory "Assorted". Import is then run separately for those tracks. There has been discussion +about this and ideas for better solutions are welcome. For now, let's assume your music tracks are located in /home/music and samplers are in /home/music/Assorted. -First, let's import the files in Assorted. This requires the flag -a to mugglei. Further flags -h, -n, -u, and -p +First, import the files in Assorted. This requires the flag -a to mugglei. Further flags -h, -n, -u, and -p specify database host, name, user and password, respectively. The filename to import is given using the -f directive. Using 'find' you can import all files for assorted albums with a command like: @@ -197,7 +188,7 @@ Muggle uses a small set of command line parameters in order to control the inter Let's look at an example: \verbatim - vdr -P'muggle -h localhost -u vdr -n GiantDisc -t/home/music' + -P'muggle -h localhost -u vdr -n GiantDisc -t/home/music' \endverbatim The -h parameter specifies the database host, -u specifies the user, -n is the database name. The scripts mentioned @@ -206,116 +197,95 @@ above do not make use of passwords, but restrict database acccess on a server ba The -t argument specifies the top level directory of the music files. On a local installation, this is the directory in which you executed the import steps (Chapter 4.2). -\section use USING MUGGLE - QUICK OVERVIEW +\section quickuse QUICK INTRO Quick version: select Muggle on the OSD, browse titles (using up/down and Ok), add them using the red button. -Then turn to the playlist view using yellow and start play using again the red function key. +Music will start instantly while you can continue to browse and add tracks. During playback, Up/Down jumps forth and back in the current playlist. Yellow toggles play/pause mode and Ok -brings up a progress display. For VDR 1.3.6- the progress display is "quite simple". +toggles a display of the replay process. Using Green, the display can be switched between playlist and single display mode, red toggles info and progress view. For VDR 1.3.6- the progress display is "quite simple", unfortunately. + +\section use DETAILED USER'S GUIDE + +The core concept of the Muggle user interface is called a *selection*. That is, as the name suggests, a selection of music tracks. Note, that a selection can be as small as a single track (a very simple selection, indeed) or as large as the whole music library. + +Selections are used to structure all tracks (the music library) into sets (e.g. a selection of all tracks by an author) and subsets (e.g. the tracks of an author on a certain album). Such selections are built by means of keys (e.g. author or album) defined in the database and are displayed in the *music browser*. The current selection in the *music browser* contains all tracks defined by the line the cursor is on. So if you place the cursor on the line "Pop", all tracks with Genre Pop are selected. If you then enter Pop and go to the line "Beatles", you narrow your selection to pop songs from the beatles. + +A collection is a special selection. Collections can be defined by the user, and he can add or remove any selection to / from a collection. A collection has only ony order: a number which is incremented for every added track. Otherwise, since a collection is also a selection, everything that is valid for selections also holds for collections. -\section use-detail USING MUGGLE +Collections can be defined by the user in the sense of a playlist. This is done by adding/removing selections to/from the *default collection*. -The idea behind muggle and the concept of the GUI was driven by the requirement, that almost all funtionalities must be possible with only using the cursor keys, the colour keys and menu, ok and back. +Changing the contents of a collection changes them directly in the data base. Saving or loading collections is not needed. -Muggle consist of three main views. The different views could always be switched with the yellow key. In every view there is a context sensitive menu with certain commands suitable for the current view. This menu could be reached from every view with the blue key. The two most common commands for a view could be reached with the remaining two colour keys red and green. +A very important term while working with Muggle is the *default collection*. This is a special collection which is the target of commands working on collections. Whenever you add selections to somewhere, they will be added to the default collection. The same happens when you remove selections. -In all views the cursor keys are used like in every VDR menu. +Another important collection is the 'play' collection. This is a temporary collection. Whatever is added to it will be played in that order. If you add something while muggle is not playing anything, this collection will first be emptied. +However 'temporary' does not mean that its content is not saved to the data base. -That is all you have to know to get full access to all functions of muggle. So now lets look in more detail in the three different views. +\subsection general General remarks -\subsection browserview BROWSER VIEW +There are two main views in Muggle, the *Music browser* view and the *Collection browser* view. You can toggle between them using the yellow key. -The Browser view will be the most used view within muggle. It presents all stored media within the database in a sort of tree view. A tree consist of nodes wich have child nodes which are also nodes. A leaf of such a tree consist of one single media file. Just to make it clear beneath a node there are on the bottom line at least on, but in the higher regions of the tree several media files. +Each of the two views has associated commands. To show a summary of the commands available for the current view press the blue key. Note, that the red, green and yellow keys do not have a fixed meaning. Rather, while the commands for a certain view are displayed, you can press red/green/yellow to make the respective key execute the command currently selected (highlighted) by the cursor. The commands you choose for red/green/yellow will be saved for the next time you start muggle. You can define different commands in both view *Music browser* and *Collection browser*. -At present there are 5 different main trees: -- artist -> album -> title -- genre -> artist -> album -> tilte -- artist -> title -- genre -> year -> title -- album -> title +\subsection browse Music browser -To add all songs from Abba to the active playlist just use the "artist -> title" tree and look for Abba in the resulting list. Press OK and here you are. All songs from Abba which are stored in the database are shown on the screen and the higlighted one could be added with the red key (you remember: most common commands are on red/green) to the active playlist. If you want to add all songs from Abba to the active playlist just go up one level, Abba is highlighted and then press red and ready you are. +By default, Muggle starts in the *Music browser* display at the place where you left it last time. This browser displays the music library according to a search order, e.g. according to artists / albums / tracks or genre / year / track. These search orders are currently fixed in the code, but the objective is to make them editable by the user on the OSD. Browsing these search orders is done using Up/Down/Left/Right keys. To display the contents of a currently selected selection, press Ok. To return to the parent selection press Back. -With the green colour key you could easily reach the main trees (this is not implemented yet). +A set of commands can be displayed with the Blue key on the remote control. A new menu will open and show the commands explained below. Remember that pressing Red, Green or Yellow will make these keys execute the command currently highlighted by the cursor from now on. -Thats all you have to know about this view. Please keep in mind that red adds always _ALL_ songs beneath the current node to the active playlist. So pressing red on one of the main trees leads to adding _ALL_ media files within the database (this could be thousands) to the active playlist. +Those commands are currently available in the *music browser*: -\subsection playlistview PLAYLIST VIEW +- Instant Play: instantly play the current selection. This does not enter any collection. -In this view you see the active playlist. All songs currently added to the playlist are shown here. With the blue colour key you can reach the context sensitive menu. Here you can find some actions to modify, load, or save the playlist, clear all entries and many others. In detail the commands are -- Red: start playing the list from the beginning or at the last played song (cf. section Replay) -- Green: Move the current entry to reorder the playlist +- Add to 'play': add the current selection to the default collection. After the first start of muggle, the default collection is 'play' -In the submenu the following commands can be selected: -- Rename playlist: change the playlist name using the up/down cursor keys -- Load playlist: show playlists in the database and load one (using Ok) -- Save playlist: Store the current playlist status into the database -- Clear playlist: Remove all entries from the playlist -- Delete current entry: Remove the currently selected entry from the playlist -- Export playlist: export the playlist in m3u (version 2) into the muggle config directory as <listname>.m3u -- Playlist commands: similar to commands.conf or reccmds.conf for VDR a file containing commands to execute on a playlist can be specified in a file playlist_commands.conf which must reside in the muggle config directory. The commands listed in that file will be called with one argument which is the path of the playlist file in m3u (version 2). +- Remove from 'play': remove the current selection from the default collection. If there are more than one instances of a specific track in the collection, they are all removed. -\subsection searchview SEARCH VIEW +- Collections: switch to the collection view -The search view is a more advanced method to look into the content of your database. If you search for all songs from Abba which are rated "++" this is your tool. You could find three different search methods which are descripte later. You switch betweeen this search methods with the green colour key. +- Select search order: select another search order -\subsubsection titlesearch TITLE SEARCH +- Export tracklist: generate a file X.m3u containing all tracks from the current selection -If you want to search for a single media file, this is for you. You could insert some search criteria and after pressing the red button you see all media files which fullfill the restrictions. +- External commands: whatever you define -Example: Search for all songs from Abba which have "water" in the title an are publisched befor 2000. +By default, the red key adds the currently selected collection to the default collection. The green key instantly plays the currently selected collection. The yellow key toggles between the *Music browser* and the *Collection browser*. Thus, if you want to play an album, browse to it and press green. Remember that you can redefine commands executed by red, green and yellow by pressing them while displaying the command list. - Artist: Abba - Title: water - Year(till): 2000 +\subsection collections Collection browser -\subsubsection albumsearch ALBUM SEARCH +The *Collection browser* displays a list of available collections. Browse the list with Up/Down and display the collection contents with Ok. Returning to the collection list is done by pressing Back. One of the collections (the one called "play" when you start up muggle for the first time) is marked with a "->" in front of the name, meaning that it is the default collection. Whenever you add or remove selections, this default collection is the current target, meaning that selections will be added/removed to/from this collection. -If you want to search for a certain album and are interested in all songs of that album even if the songs not fullfill the search criteria, than this search is for you. +At the bottom of the list, the entry "Create collection" is displayed. Entering it with the right key will make the editor appear on the second half of the line and using the keys Up/Down/Left/Right you can enter the name of the new collection. Pressing Ok will terminate the editing process and add the new collection to the list. -Example: Search for all songs on an album from "U2" with "tree" in the album title. +Just like with the *music browser*, a set of commands can be displayed with the Blue key on the remote control. - Album Title: tree - Album Artist: U2 +Those commands are currently available in the list of collections. Depending on the current selection, not all of them are available: -\subsubsection playlistsearch PLAYLIST SEARCH +- Instant play: See *music browser* -If you want to search for a playlist wích contains songs which fullfill the criteria. +- Add to 'play': See *music browser* -Example: Yesterday during your birthday party you played a song from Tina Turner during your party, your best friend is interested in the song which was played just behind that Tina Turner song. The search presents all titles of that playlist. You could easily browser the list, find Tinas song and you have the one behind. +- Remove from 'play': See *music browser*. Not available when the cursor is on the default collection. - Playlist Title: Birthday Party 2004 - Artist: Tina Turner +- Remove all entries from 'play': Only available when the cursor is on the default collection. -\subsection useplayer DURING PLAYBACK +- Search: switch to the *music browser* -The functions available furing playback mostly relate to navigation in the playlist -and displaying information about the current track or playlist. +- Set default collection to 'X': as it says. -- Up: Skip to the next title -- Down: Skip to the previous title -- Ok: toggle display (progress or information view) +- Delete collection: Not available for the default collection and for the 'play' collection. -- Red: When display shown: toggle between progress and information view, otherwise toggle loop mode (not yet functional) -- Green: When display shown: toggle between track and playlist view, otherwise toggle shuffle mode (not yet functional) -- Yellow: Play/Pause -- Blue: stop replay but remind resume index (so Play from browser view starts with the track played last) +- Export track list: See *music browser* -- Back: stop replay (Play from browser will start from the beginning) +- External commands: whatever you define -Alternativ Key Settings: +Note that you cannot only add to/remove from collections in the *music browser*. Rather, also collections can be added/removed. The reason is that - as explained above - a collection is also a selection. So everything that can be done with selections can also be done with collections. An example: if you want to give a party, you could create a new collection "Party". Now, steer your cursor to the collection entitled "Lounge music" and select add. Then go to "Pop 80s" and add again. Finally, go to "Dance classics" and add. Now you have created a collection "Party" from three already existing collections. To continue this example, let us assume that one of your guests has a personal dislike against "Modern Talking". Switch to the browser view, go to the artist selection of "Modern Talking" and select "Remove". Now all tracks written by Modern Talking will be removed from your "Party" collection. -- Down: Pause -- Up: Play -- Right: Fast Forward -- Left: Rewind +Please note that "Remove" means removing from the default collection. "Delete" will delete a collection. -- Ok: toggle Display (Progress View -> Information -> View -> Playlist View -> Off) +It is possible that a collection holds the same track several times if you add it several times. However when you remove that track, all of its occurrences will be removed. -- Red -- Green: Skip to previous song. -- Yellow: Skip to next song. -- Blue: stop replay but remind resume index (so Play from browser view starts with the track played last) +The remote buttons Play, Pause, Stop are also supported while muggle displays its OSD. If Stop is pressed, muggle first stops playing what was started by Instant Play. Muggle will then continue playing the 'play' collection. A second Stop will stop playing the 'play' collection. */ @@ -10,36 +10,39 @@ \section urgent Urgent/Short-term issues \subsection bugs Bugs and testing needed - - Crashes in filter selections? - - Keep this? Test mgPCMPlayer::getSourceFile() for GD case (find) \subsection urgentosd OSD-related Issues - - Rename playlist - \subsection urgentplayer Player extensions - - Display covers - - Import filename - - Show image during replay - - Add FLAC decoder - - Determine max. framecount (needed for rewinding?) - - Init scale/level/normalize? - - The max. level should be recognized during play - - Store max. level in the database + \subsection urgentcode Code polishing + + - Clean up coding style and documentation in general + - Logging + - extend mgLog with static logging methods + - in DEBUG mode, issue logs, warnings, errors to stderr + - otherwise issue errors only to syslog + - Check for unnecessary log commands + - Generate HTML documentation using doxygen, + - use dotty/gv for state machines of player + - make available online + - Clean up mugglei (abstract code where possible) + - extend mgSelection with all SQL code needed by mugglei.c, maybe something like mgSelection.SyncWithPath(const char *path, options) + - then remove all SQL code from mugglei.c + - Check for memory leaks + - Check for (reasonably) consistent usage of char pointers and strings + - mgPlayer used what for? + - Could save IP/host name and associate last playlist/index loaded \subsection urgentcontent Content handling - - Think, whether type (mp3, ogg, flac) should be stored in database + - Save on exit + - Think, whether type (mp3, ogg, flac) should be stored in database - could be used in searching/structuring as well - - Handle shuffle mode in mgPlaylist - - shuffle mode on - - for next file: - - generate a set of random numbers as long as the playlist - - re-generate when removing or adding entries - - in mgPlaylist::getCurrent use this additional set as a mapping - Party mode (see iTunes) - initialization - find 15 titles according to the scheme below - playing - before entering next title perform track selection + - do not increment the playcount + - if more than 5 titles are found, make sure the same title is not directly repeated - track selection - generate a random uid - if file exists: @@ -48,30 +51,19 @@ - if n < playcount / max. playcount - add the file to the end of the list + \subsection urgentplayer Player extensions + - Possible to resume play instead of restarting list from the beginning? + - Display covers + - Import filename + - Show image during replay + - Add FLAC decoder + - Determine max. framecount (needed for rewinding?) + - Init scale/level/normalize? + - The max. level should be recognized during play + - Store max. level in the database + - Display covers - \subsection urgentcode Code polishing - - Clean up coding style and documentation in general - - Logging - - extend mgLog with static logging methods - - in DEBUG mode, issue logs, warnings, errors to stderr - - otherwise issue errors only to syslog - - Check for unnecessary log commands - - Generate HTML documentation using doxygen, - - use dotty/gv for state machines of player - - make available online - - Clean up mugglei (abstract code where possible) - - Check for memory leaks - - Why do filters use pointers? - - Check for (reasonably) consistent usage of char pointers and strings - - mgDatabase class is not used - - should handle a static object with a MySQL connection - - execute queries - - escape query strings - - mgPlayer used what for? - - Could save IP/host name and associate last playlist/index loaded - - Move differences 1.3.7+- to link level - - \subsection deploy Deployment + \subsection deploy Deployment - Script to publish a version - make dist @@ -118,7 +110,7 @@ - Cover text - Tracks - - Language (?) + - Language (?) - encoded by what standard? - Rating? - Modified, created - Lyrics @@ -128,23 +120,27 @@ \subsection midcode Code issues - - really abstract from specific queries etc. - - mgDatabase should completely abstract database (mySQL) stuff!? - - initialization - - read/write queries - - return results (needs an abstract representation of results?) - - \subsection midosd OSD-related issues - + \subsection midosd OSD-related issues + - can mgMenu and mgMainMenu be combined into one class? + - can mgActions inherit from cOsdItem? - Incremental search - Type numbers to enter characters and jump to first title accordingly - Check whether submenus (as implemented in VDR) are more suitable - do not permit jumping to arbitrary menus though - \subsection midcontent Content issues - - - Save/load filter sets - - Apply filter set as dynamic playlist (i.e. show filters when loading playlists) + \subsection midcontent filter issues + + - new OSD list for filters. Only ONE filter can be active at any time + - filters can be defined recursively, different filters can share subfilters + - Save/load filter set + - table filters and filterrules + - filters: id, name, created, author. Uses id 0 from filterrules. + - filterrules: PRIMARY(filterid, id), negate, operator, op1, op2, + - if operator is AND or OR, op1 and op2 are filterrule ids + - filters are always applied, even to "instant play" and "now playing" + + \subsection midcontent Content issues + - Handle ratings (increase/decrease during replay) - Keys to directly increase - handle a playcounter @@ -153,12 +149,12 @@ - when playfrequency reaches upper level y from above: decrease rating - when playfrequency reaches upper level y from below: increase rating - \subsection midplayer Player issues + \subsection midplayer Player issues - Use single CD files with cuesheets in metadata for FLAC - Handle recoding samplerate, limiter etc correctly - \section vision Long term ideas and visions + \section vision Long term ideas and visions - daapd integration? - netjuke integration? @@ -237,16 +233,5 @@ - Toggle detail/progress view (green) - Track view: all metadata - Playlist view: all tracks (past three, upcoming ones) - - BUG: Red key does not work in Browser submenu - - Test execution of playlist commands - - Resume play instead of restarting list from the beginning when terminating play with blue instead of back - - Save current playlist on exit and restore on start - - Save on exit - - implement storePlaylistAs( string name ) in gdPlaylist - - find id of playlist called name or create it - - delete all entries - - store current entries - - Handle loop mode in mgPlaylist - - Track/progress view settings are now saved in the setup - - BUG: could not delete first track of a playlist -*/
\ No newline at end of file + +*/ diff --git a/gd_content_interface.c b/gd_content_interface.c deleted file mode 100644 index 890f2b6..0000000 --- a/gd_content_interface.c +++ /dev/null @@ -1,1425 +0,0 @@ -/*! - * \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$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author$ - * - * Implements main classes of for content items and interfaces to SQL databases - * - * This file implements the following classes - * - GdPlaylist a playlist - * - mgGdTrack a single track (content item). e.g. an mp3 file - * - mgSelection a set of tracks (e.g. a database subset matching certain criteria) - */ - -#define DEBUG - -#include "gd_content_interface.h" - -#include "mg_tools.h" -#include "mg_database.h" -#include "vdr_setup.h" - -#include "i18n.h" - -#define GD_PLAYLIST_TYPE 0 //< listtype for giant disc db - -// some dummies to keep the compiler happy -#define DUMMY_CONDITION true // we use that as dummy condition to satisfy C++ syntax -#define DUMMY - -/*! - * \brief initialize a database used by Giantdisc - * - * \todo should be a static function in some Gd class - */ -int GdInitDatabase( MYSQL *db ) -{ - if( mysql_init(db) == NULL ) - { - return -1; - } - - if (the_setup.DbSocket != NULL) - { - mgDebug(1,"Using sockets for connecting to Database."); - - //mgDebug(3,"Socket is: '%s'",the_setup.DbSocket); - //mgDebug(3,"DbUser is: '%s'",the_setup.DbUser); - //mgDebug(3,"DbPassword is: '%s'",the_setup.DbPass); - - if( mysql_real_connect( db, - "", - the_setup.DbUser, - the_setup.DbPass, - the_setup.DbName, - 0, - the_setup.DbSocket, 0 ) == NULL ) - { - return -2; - } // if mysql_real_connect - } //if DbSocket - else - { - mgDebug(1,"Using TCP-host for connecting to Database."); - if( mysql_real_connect( db, - the_setup.DbHost, - the_setup.DbUser, - the_setup.DbPass, - the_setup.DbName, - the_setup.DbPort, - NULL, 0 ) == NULL ) - { - return -2; - } // if mysql_real_connect - } // else (if DbSocket) - - return 0; -} - -std::vector<std::string> *GdGetStoredPlaylists(MYSQL db) -{ - std::vector<std::string>* list = new std::vector<std::string>(); - MYSQL_RES *result; - MYSQL_ROW row; - - result = mgSqlReadQuery(&db, "SELECT title FROM playlist"); - - while( (row = mysql_fetch_row(result) ) != NULL ) - { - list->push_back(row[0]); - } - return list; -} - -gdFilterSets::gdFilterSets() -{ - mgFilter* filter; - std::vector<mgFilter*>* set; - std::vector<std::string>* rating; - m_titles.push_back( tr("Track Search") ); - - // create an initial set of filters with empty values - set = new std::vector<mgFilter*>(); - rating = new std::vector<std::string>(); - rating->push_back("-"); - rating->push_back("O"); - rating->push_back("+"); - rating->push_back("++"); - - // year-from - filter = new mgFilterInt(tr("year (from)"), 1901, 1900, 2100); set->push_back(filter); - - // year-to - filter = new mgFilterInt(tr("year (to)"), 2099, 1900, 2100); set->push_back(filter); - - // title - filter = new mgFilterString(tr("title"), ""); set->push_back(filter); - - // artist - filter = new mgFilterString(tr("artist"), ""); set->push_back(filter); - - // genre - filter = new mgFilterString(tr("genre"), ""); set->push_back(filter); - - // rating. TODO: Currently buggy. LVW - // filter = new mgFilterChoice(tr("rating"), 1, rating); set->push_back(filter); - - m_sets.push_back(set); - - m_titles.push_back(tr("Album Search")); - - set = new std::vector<mgFilter*>(); - // year-from - filter = new mgFilterInt(tr("year (from)"), 1901, 1900, 2100); set->push_back(filter); - // year-to - filter = new mgFilterInt(tr("year (to)"), 2099, 1900, 2100); set->push_back(filter); - // title - filter = new mgFilterString(tr("album title"), ""); set->push_back(filter); - // artist - filter = new mgFilterString(tr("album artist"), ""); set->push_back(filter); - // genre - filter = new mgFilterString(tr("genre"), ""); set->push_back(filter); - // rating - filter = new mgFilterChoice(tr("rating"), 1, rating); set->push_back(filter); - - m_sets.push_back(set); - - m_titles.push_back(tr("Playlist Search")); - - set = new std::vector<mgFilter*>(); - // year-from - filter = new mgFilterInt(tr("year (from)"), 1901, 1900, 2100); set->push_back(filter); - // year-to - filter = new mgFilterInt(tr("year (to)"), 2099, 1900, 2100); set->push_back(filter); - // title - filter = new mgFilterString(tr("playlist title"), ""); set->push_back(filter); - // artist - filter = new mgFilterString(tr("playlist author"), ""); set->push_back(filter); - // title - filter = new mgFilterString(tr("title"), ""); set->push_back(filter); - // artist - filter = new mgFilterString(tr("artist"), ""); set->push_back(filter); - // genre - filter = new mgFilterString(tr("genre"), ""); set->push_back(filter); - // rating - filter = new mgFilterChoice(tr("rating"), 1, rating); set->push_back(filter); - - m_sets.push_back(set); - - m_activeSetId = 0; - m_activeSet = m_sets[m_activeSetId]; -} - -gdFilterSets::~gdFilterSets() -{ - // everything is done in the destructor of the base class -} - -std::string gdFilterSets::computeRestriction(int *viewPrt) -{ - std::string sql_str = "1"; - - switch( m_activeSetId ) - { - case 0: - { - // tracks (flatlist for mountain man ;-)) - *viewPrt = 100; - } break; - case 1: - { - // album -> tracks - *viewPrt = 101; - } break; - case 2: - { - // playlist -> tracks - *viewPrt = 102; - } break; - default: - { - mgWarning( "Ignoring Filter Set %i", m_activeSetId ); - } break; - } - - for( std::vector<mgFilter*>::iterator iter = m_activeSet->begin(); - iter != m_activeSet->end(); - iter++ ) - { - if( (*iter)->isSet() ) - { - if( strcmp((*iter)->getName(), tr("playlist title") ) == 0 ) - { - sql_str = sql_str + " AND playlist.title like '%%" - + (*iter)->getStrVal() + "%%'"; - } - else if(strcmp( (*iter)->getName(), tr("playlist author") ) == 0 ) - { - sql_str = sql_str + " AND playlist.author like '%%" - + (*iter)->getStrVal() + "%%'"; - } - else if(strcmp((*iter)->getName(), tr("album title")) == 0 ) - { - sql_str = sql_str + " AND album.title like '%%" - + (*iter)->getStrVal() + "%%'"; - } - else if(strcmp((*iter)->getName(), tr("album artist")) == 0 ) - { - sql_str = sql_str + " AND album.artist like '%%" - + (*iter)->getStrVal() + "%%'"; - } - else if(strcmp((*iter)->getName(), tr("title")) == 0 ) - { - sql_str = sql_str + " AND tracks.title like '%%" - + (*iter)->getStrVal() + "%%'"; - } - else if(strcmp((*iter)->getName(), tr("artist")) == 0 ) - { - sql_str = sql_str + " AND tracks.artist like '%%" - + (*iter)->getStrVal() + "%%'"; - } - else if(strcmp((*iter)->getName(), tr("genre")) == 0 ) - { - sql_str = sql_str + " AND (genre1.genre like '" - + (*iter)->getStrVal() + "'"; - sql_str = sql_str + " OR genre2.genre like '" - + (*iter)->getStrVal() + "')"; - } - else if(strcmp((*iter)->getName(), tr("year (from)")) == 0 ) - { - sql_str = sql_str + " AND tracks.year >= " + (*iter)->getStrVal(); - } - else if(strcmp((*iter)->getName(), tr("year (to)")) == 0 ) - { - sql_str = sql_str + " AND tracks.year <= " + (*iter)->getStrVal(); - } - else if(strcmp((*iter)->getName(), tr("rating")) == 0 ) - { - if ((*iter)->getStrVal() == "-") - { - sql_str = sql_str + " AND tracks.rating >= 0 "; - } - else if ((*iter)->getStrVal() == "O") - { - sql_str = sql_str + " AND tracks.rating >= 1 "; - } - else if ((*iter)->getStrVal() == "+") - { - sql_str = sql_str + " AND tracks.rating >= 2 "; - } - else if ((*iter)->getStrVal() == "++") - { - sql_str = sql_str + " AND tracks.rating >= 3 "; - } - } - else - { - mgWarning( "Ignoring unknown filter %s", (*iter)->getName() ); - } - } - } - mgDebug(1, "Applying sql std::string %s (view=%d)", sql_str.c_str(), *viewPrt ); - return sql_str; -} - -mgGdTrack mgGdTrack::UNDEFINED = mgGdTrack(); - -mgGdTrack::mgGdTrack( int sqlIdentifier, MYSQL dbase ) -{ - m_uniqID = sqlIdentifier; - m_db = dbase; - m_retrieved = false; -} - -mgGdTrack::mgGdTrack(const mgGdTrack& org) -{ - m_uniqID = org.m_uniqID; - m_db = org.m_db; - m_retrieved = org.m_retrieved; - - if( m_retrieved ) - { - m_artist = org.m_artist; - m_title = org.m_title; - m_mp3file = org.m_mp3file; - m_album = org.m_album; - m_genre = org.m_genre; - m_year = org.m_year; - m_rating = org.m_rating; - m_length = org.m_length; - } -} - -mgGdTrack::~mgGdTrack() -{ - // nothing to be done -} - -bool mgGdTrack::readData() -{ - MYSQL_RES *result; - int nrows, nfields; - - // 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, tracks.samplerate, tracks.channels, tracks.bitrate " - "FROM tracks, album, genre " - "WHERE tracks.id = %d " - "AND album.cddbid = tracks.sourceid AND " - "genre.id = tracks.genre1", - m_uniqID ); - - nrows = mysql_num_rows(result); - nfields = mysql_num_fields(result); - - if( nrows == 0 ) - { - mgWarning( "No entries found \n" ); - return false; - } - else - { - 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 = std::string( the_setup.ToplevelDir ) + row[3]; - m_genre = row[4]; - - if( sscanf( row[5], "%d", &m_year) != 1 ) - { - mgError("Invalid year '%s' in database", row [5]); - } - - if( row[6] && sscanf( row[6], "%d", &m_rating ) != 1 ) - { - mgError( "Invalid rating '%s' in database", row [6] ); - } - - if( row[7] && sscanf( row[7], "%d", &m_length) != 1 ) - { - 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; -} - -std::string mgGdTrack::getSourceFile() -{ - if( !m_retrieved ) - { - readData(); - } - return m_mp3file; -} - -std::string mgGdTrack::getTitle() -{ - if( !m_retrieved ) - { - readData(); - } - return m_title; -} - -std::string mgGdTrack::getArtist() -{ - if(!m_retrieved) - { - readData(); - } - return m_artist; -} - -int mgGdTrack::getLength() -{ - if( !m_retrieved ) - { - readData(); - } - return m_length; -} - - -std::string mgGdTrack::getLabel(int col) -{ - if( !m_retrieved ) - { - readData(); - } - switch(col) - { - case 0: - return m_title; - case 1: - return m_artist; - case 2: - return m_album; - case 3: - return m_genre; - default: - return ""; - } -} - -std::vector<mgFilter*> *mgGdTrack::getTrackInfo() -{ - return new std::vector<mgFilter*>(); -} - -bool mgGdTrack::setTrackInfo(std::vector<mgFilter*> *info) -{ - return false; -} - -std::string mgGdTrack::getAlbum() -{ - if( !m_retrieved ) - { - readData(); - } - return m_album; -} - -std::string mgGdTrack::getGenre() -{ - if(!m_retrieved) - { - readData(); - } - return m_genre; -} - -int mgGdTrack::getYear() -{ - if(!m_retrieved) - { - readData(); - } - return m_year; -} - -int mgGdTrack::getRating() -{ - if(!m_retrieved) - { - readData(); - } - return m_rating; -} - -int mgGdTrack::getDuration() -{ - if(!m_retrieved) - { - readData(); - } - return m_rating; -} - -int mgGdTrack::getSampleRate() -{ - if(!m_retrieved) - { - readData(); - } - return m_samplerate; -} - -int mgGdTrack::getChannels() -{ - if(!m_retrieved) - { - readData(); - } - return m_channels; -} - -std::string mgGdTrack::getBitrate() -{ - if(!m_retrieved) - { - readData(); - } - return m_bitrate; -} - -std::string mgGdTrack::getImageFile() -{ - return "dummyImg.jpg"; -} - -void mgGdTrack::setTitle(std::string new_title) -{ - m_title = new_title; -} - -void mgGdTrack::setArtist(std::string new_artist) -{ - m_artist = new_artist; -} - -void mgGdTrack::setAlbum(std::string new_album) -{ - m_album = new_album; -} - -void mgGdTrack::setGenre(std::string new_genre) -{ - m_genre = new_genre; -} - -void mgGdTrack::setYear(int new_year) -{ - m_year = new_year; -} - -void mgGdTrack::setRating(int new_rating) -{ - m_rating = new_rating; -} - -bool mgGdTrack::writeData() -{ - mgSqlWriteQuery( &m_db, "UPDATE tracks " - "SET artist=\"%s\", title=\"%s\", year=%d, rating=%d " - "WHERE id=%d", - m_artist.c_str(), m_title.c_str(), - m_year, m_rating, m_uniqID); - return true; -} - -GdTracklist::GdTracklist(MYSQL db_handle, std::string restrictions) -{ - MYSQL_RES *result; - MYSQL_ROW row; - int trackid; - - result = mgSqlReadQuery( &db_handle, - "SELECT tracks.id " - " FROM tracks, album, genre WHERE %s" - " AND album.cddbid=tracks.sourceid " - " AND genre.id=tracks.genre1", - restrictions.c_str()); - - while( ( row = mysql_fetch_row(result) ) != NULL ) - { - // row[0] is the trackid - if(sscanf(row[0], "%d", &trackid) != 1) - { - mgError("Can not extract integer track id from '%s'", - row[0]); - } - m_list.push_back(new mgGdTrack(trackid, db_handle)); - } -} - -GdPlaylist::GdPlaylist(std::string listname, MYSQL db_handle) -{ - MYSQL_RES *result; - MYSQL_ROW row; - int nrows; - - m_db = db_handle; - - // - // check, if the playlist already exists - // - result = mgSqlReadQuery(&m_db, - "SELECT id,author FROM playlist where title=\"%s\"", - listname.c_str()); - nrows = mysql_num_rows(result); - - if( nrows == 0 ) - { - mgDebug(3, "No playlist with name %s found. Creating new playlist\n", - listname.c_str()); - - // create new database entry - mgSqlWriteQuery( &m_db, "INSERT into playlist " - "SET title=\"%s\", author=\"%s\"", - listname.c_str(), - "VDR", // default author - ""); // creates current time as timestamp - m_author = "VDR"; - m_listname = listname; - - // now read thenew list to get the id - result = mgSqlReadQuery( &m_db, - "SELECT id,author FROM playlist where title=\"%s\"", - listname.c_str() ); - nrows = mysql_num_rows(result); - row = mysql_fetch_row(result); - - if( sscanf(row [0], "%d", & m_sqlId) !=1 ) - { - mgError("Invalid id '%s' in database", row [5]); - } - } - else - { // playlist exists, read data - row = mysql_fetch_row(result); - - if( sscanf(row[0], "%d", & m_sqlId) !=1 ) - { - mgError("Invalid id '%s' in database", row [5]); - } - - m_author = row[1]; - m_listname = listname; - - // now read allentries of the playlist and - // write them into the tracklist - insertDataFromSQL(); - - } // end 'else (playlist exists) - - m_listtype = GD_PLAYLIST_TYPE; // GiantDB list type for playlists -} - -GdPlaylist::~GdPlaylist() -{ -} - -void GdPlaylist::setListname(std::string name) -{ - m_listname = name; - m_sqlId = -1; -} - -int GdPlaylist::insertDataFromSQL() -{ - MYSQL_RES *result; - MYSQL_ROW row; - mgGdTrack* trackptr; - int id; - int nrows; - - result = mgSqlReadQuery( &m_db, - "SELECT tracknumber, trackid FROM playlistitem " - "WHERE playlist = %d ORDER BY tracknumber", - m_sqlId); - nrows = mysql_num_rows(result); - while( (row = mysql_fetch_row(result) ) != NULL ) - { - // add antry to tracklist - if( sscanf( row[1], "%d", &id ) !=1 ) - { - mgWarning( "Track id '%s' is not an integer. Ignoring \n", row[1] ); - } - else - { - trackptr = new mgGdTrack( id, m_db ); - m_list.push_back( trackptr ); - } - } - return nrows; -} - -bool GdPlaylist::storePlaylist() -{ - std::vector<mgContentItem*>::iterator iter; - int num; - MYSQL_RES *result; - MYSQL_ROW row; - int nrows; - - if( m_listname == " " ) - { - mgWarning("Can not store Tracklist without name"); - return false; - } - - if( m_sqlId >= 0 ) - { - // playlist alreay exists in SQL database - // remove old items first - // cout << " GdPlaylist::storePlaylist: removing items from " << m_sqlId << flush; - - // remove old playlist items from db - mgSqlWriteQuery(&m_db, - "DELETE FROM playlistitem WHERE playlist = %d", - m_sqlId); - } - else - { - // create new database entry - mgSqlWriteQuery(&m_db, "INSERT into playlist " - "SET title=\"%s\", author=\"%s\"", - m_listname.c_str(), - "VDR", // default author - ""); // creates current time as timestamp - m_author = "VDR"; - - // now read the new list to get the id - result = mgSqlReadQuery( &m_db, - "SELECT id,author FROM playlist where title=\"%s\"", - m_listname.c_str()); - nrows = mysql_num_rows(result); - row = mysql_fetch_row(result); - - if( sscanf( row [0], "%d", & m_sqlId ) !=1 ) - { - mgError("Invalid id '%s' in database", row [5]); - } - } - - // add new playlist items to db - for( iter=m_list.begin(), num=0; - iter != m_list.end(); - iter++, num++) - { - mgSqlWriteQuery(&m_db, - "INSERT into playlistitem " - "SET tracknumber=\"%d\", trackid=\"%d\", playlist=%d", - num, (*iter)->getId(), m_sqlId); - } - return true; -} - -bool GdPlaylist::storeAs( std::string name ) -{ - int id; - MYSQL_ROW row; - MYSQL_RES *result; - - mgDebug( 1, "GdPlaylist::storeAs" ); - - result = mgSqlReadQuery( &m_db, - "SELECT id FROM playlist WHERE title=\"%s\"", - name.c_str() ); - - if( mysql_num_rows(result) ) - { - row = mysql_fetch_row( result ); - mgDebug( 1, "GdPlaylist::storeAs: found playlist" ); - } - else - { - // otherwise create a new database entry - mgSqlWriteQuery( &m_db, "INSERT into playlist SET " - "title=\"%s\", author=\"VDR\"", - name.c_str() ); - - // now read the new list to get the id - result = mgSqlReadQuery( &m_db, - "SELECT id,author FROM playlist where title=\"%s\"", - name.c_str() ); - - row = mysql_fetch_row(result); - mgDebug( 1, "GdPlaylist::storeAs: created playlist" ); - } - - if( sscanf( row [0], "%d", &id ) !=1 ) - { - mgError("Invalid id '%s' in database", row [5]); - } - else - { - // now we know that the playlist 'name' has identifier id - - // remove old playlist items from db - mgSqlWriteQuery( &m_db, - "DELETE FROM playlistitem WHERE playlist = %d", - id ); - - // add new playlist items to db - std::vector<mgContentItem*>::iterator iter; - int num = 0; - for( iter=m_list.begin(), num=0; - iter != m_list.end(); - iter++, num++) - { - mgDebug( 1, "GdPlaylist::storeAs: inserting track" ); - mgSqlWriteQuery(&m_db, - "INSERT into playlistitem " - "SET tracknumber=\"%d\", trackid=\"%d\", playlist=%d", - num, (*iter)->getId(), id ); - } - } - return true; -} - -/*! - * \brief returns the total duration of all songs in the list in seconds - */ -int GdPlaylist::getPlayTime() -{ - //DUMMY - // go over all entries in the playlist and accumulate their playtime - - return 0; -} - -/*! - * \brief returns the duration of all remaining songs in the list in seconds - */ -int GdPlaylist::getPlayTimeRemaining() -{ - //DUMMY - // go over all remaining entries in the playlist and accumulate their - // playtime - // The remaining playtime of the current song is only known by the mplayer - return 0; // dummy -} - -/*! - * \brief constructor - */ -GdTreeNode::GdTreeNode(MYSQL db, int view, std::string filters) - : mgSelectionTreeNode(db, view) -{ - // create a root node - // everything is done in the parent class - m_restriction = filters; - m_view = view; - m_label = tr("Browser"); -} - -GdTreeNode::GdTreeNode( mgSelectionTreeNode* parent, - std::string id, - std::string label, - std::string restriction ) - : mgSelectionTreeNode(parent, id, label) -{ - m_restriction = restriction; - // everything else is done in the parent class -} - -/*! - * \brief destructor - */ -GdTreeNode::~GdTreeNode() -{ - // _children.clear(); -} - -/*! - * \brief checks if this node can be further expandded or not - * \true, if node ia leaf node, false if node can be expanded - */ -bool GdTreeNode::isLeafNode() -{ - if( m_level == 0 ) - { - return false; - } - - switch(m_view) - { - case 1: // artist -> album -> title - if( m_level <= 3 ) - { - return false; - } - break; - case 2: // genre -> artist -> album -> track - if( m_level <= 3 ) - { - return false; - } - break; - case 3: // Artist -> Track - if( m_level <= 2 ) - { - return false; - } - break; - case 4: - if( m_level <= 2 ) - { - return false; - } - break; - case 5: - if( m_level <= 1 ) - { - return false; - } - break; - case 100: - if( m_level <= 0 ) - { - return false; - } - break; - case 101: - if( m_level <= 1 ) - { - return false; - } - break; - case 102: - if( m_level <= 1 ) - { - return false; - } - break; - default: - mgError("View '%d' not yet implemented", m_view); - } - return true; -} - -/*! - * \brief compute children on the fly - * - * \return: true, if the node could be expanded (or was already), false,of - * node can not be expanded any further - * - * retrieves all entries for the next level that satisfy the restriction of - * the current level and create a child-arc for each distinct entry - * - * \todo use asnprintf! - */ -bool GdTreeNode::expand() -{ - MYSQL_ROW row; - MYSQL_RES *result; - int nrows; - int nfields; - char sqlbuff[1024]; /* hope it's big enough ! */ - char idbuf[255]; - int numchild; - - std::string labelfield; // human readable db field for the column to be expanded - std::string idfield; // unique id field for the column to be expanded - std::string new_restriction_field; // field to be restricted by the new level - std::string new_restriction; // complete restriction str for the current child - std::string new_label; - GdTreeNode* new_child; - - std::string tables; // stores the db tables used - -#define FROMJOIN " FROM tracks, genre as genre1, genre as genre2, album WHERE tracks.sourceid=album.cddbid AND genre1.id=tracks.genre1 AND genre2.id=tracks.genre2 AND %s " - - if( m_expanded ) - { - mgWarning("Node already expanded\n"); - return true; - } - - if( m_level == 1 && m_view < 100 ) - { - m_view = atoi( m_id.c_str() ); - } - - mgDebug( 5, "Expanding level %d view %d\n", m_level, m_view ); - if( m_level > 0 ) - { - switch( m_view ) - { - case 1: - { // artist -> album -> title - if( m_level == 1 ) - { - sprintf( sqlbuff, - "SELECT DISTINCT album.artist,album.artist" - FROMJOIN - " ORDER BY album.artist" - , m_restriction.c_str() ); - idfield = "album.artist"; - } - else if( m_level == 2 ) - { // artist -> album - sprintf(sqlbuff, - "SELECT DISTINCT album.title,album.cddbid" - FROMJOIN - " ORDER BY album.title" - , m_restriction.c_str() ); - idfield = "album.cddbid"; - } - else if(m_level == 3) - { // album -> title - sprintf(sqlbuff, - "SELECT tracks.title,tracks.id" - FROMJOIN - " ORDER BY tracks.tracknb" - , m_restriction.c_str() ); - idfield = "tracks.id"; - } - else - { - mgWarning("View #%d level %d' not yet implemented", m_view, m_level); - m_expanded = false; - return false; - } - } break; - case 2: - { // genre -> artist -> album -> track - if( m_level == 1 ) - { // genre - sprintf(sqlbuff, - "SELECT DISTINCT genre1.genre,tracks.genre1" - FROMJOIN - " ORDER BY genre1.id" - , m_restriction.c_str()); - idfield = "tracks.genre1"; - } - else if( m_level == 2 ) - { // genre -> artist - sprintf(sqlbuff, - "SELECT DISTINCT album.artist,album.artist" - FROMJOIN - " ORDER BY album.artist", - m_restriction.c_str()); - idfield = "album.artist"; - } - else if( m_level == 3 ) - { // genre -> artist -> album - sprintf(sqlbuff, - "SELECT DISTINCT album.title,tracks.sourceid" - FROMJOIN - " ORDER BY album.title" - , m_restriction.c_str()); - idfield = "tracks.sourceid"; - } - else if( m_level == 4 ) - { // genre -> artist -> album -> track - sprintf(sqlbuff, - "SELECT DISTINCT tracks.title, tracks.id" - FROMJOIN - " ORDER BY tracks.tracknb" - , m_restriction.c_str()); - idfield = "tracks.id"; - } - else - { - mgWarning("View #%d level %d' not yet implemented", m_view, m_level); - m_expanded = false; - return false; - } - } break; - case 3: - { // Artist -> Track - if( m_level ==1 ) - { - sprintf( sqlbuff, - "SELECT DISTINCT tracks.artist,tracks.artist" - FROMJOIN - " ORDER BY tracks.artist", - m_restriction.c_str()); - idfield = "tracks.artist"; - } - else if( m_level == 2) - { // Track - sprintf(sqlbuff, - "SELECT DISTINCT tracks.title,tracks.id" - FROMJOIN - " ORDER BY tracks.title", - m_restriction.c_str()); - idfield = "tracks.id"; - } - else - { - mgWarning("View #%d level %d' not yet implemented", m_view, m_level); - m_expanded = false; - return false; - } - } break; - case 4: - { // Genre -> Year -> Track - if( m_level == 1 ) - { // Genre - sprintf(sqlbuff, - "SELECT DISTINCT genre1.genre,tracks.genre1" - FROMJOIN - " ORDER BY genre1.genre", - m_restriction.c_str()); - idfield = "tracks.genre1"; - } - else if (m_level == 2) - { // Year - sprintf(sqlbuff, - "SELECT DISTINCT tracks.year,tracks.year" - FROMJOIN - " ORDER BY tracks.year" - , m_restriction.c_str()); - idfield = "tracks.year"; - } - else if( m_level == 3 ) - { // Track - sprintf(sqlbuff, - "SELECT DISTINCT" - " CONCAT(tracks.artist,' - ',tracks.title) AS title" - " ,tracks.id" - FROMJOIN - " ORDER BY title", - //" ORDER BY tracks.title", - m_restriction.c_str()); - idfield = "tracks.id"; - } - else - { - mgWarning("View #%d level %d' not yet implemented", m_view, m_level); - m_expanded = false; - return false; - } - } break; - case 5: // Album -> Tracks - if( m_level == 1 ) - { // Album - sprintf(sqlbuff, - "SELECT DISTINCT" - " CONCAT(album.artist,' - ',album.title) AS title," - " album.cddbid" - FROMJOIN - " ORDER BY title" - , m_restriction.c_str()); - idfield = "tracks.sourceid"; - } - else if (m_level == 2) - { // Track - sprintf(sqlbuff, - "SELECT DISTINCT tracks.title, tracks.id" - FROMJOIN - " ORDER BY tracks.tracknb", - m_restriction.c_str()); - idfield = "tracks.id"; - } - else - { - mgWarning("View #%d level %d' not yet implemented", m_view, m_level); - m_expanded = false; - return false; - } - break; - case 100: - if (m_level == 1) - { - sprintf(sqlbuff, - "SELECT CONCAT(tracks.artist,' - ',tracks.title)," - " tracks.id" - FROMJOIN - " ORDER BY CONCAT(tracks.artist,' - ',tracks.title)" - , m_restriction.c_str()); - idfield = "tracks.id"; - } - else - { - mgWarning( "View #%d level %d' not yet implemented", m_view, m_level ); - m_expanded = false; - return false; - } - break; - case 101: - { // Albumsearch result - if( m_level == 1 ) - { - sprintf(sqlbuff, - "SELECT DISTINCT" - " CONCAT(album.artist,' - ',album.title) as title," - " album.cddbid" - FROMJOIN - " ORDER BY CONCAT(album.artist,' - ',album.title)", - m_restriction.c_str()); - idfield = "tracks.sourceid"; - } - else if( m_level == 2 ) - { - sprintf(sqlbuff, - "SELECT tracks.title,tracks.id" - FROMJOIN - " ORDER BY tracks.tracknb", - m_restriction.c_str()); - idfield = "tracks.id"; - } - else - { - mgWarning("View #%d level %d' not yet implemented", m_view, m_level); - m_expanded = false; - return false; - } - } break; - case 102: - { - if (m_level == 1) - { - sprintf(sqlbuff, - "SELECT DISTINCT playlist.title," - " playlist.id" - " FROM playlist,playlistitem,tracks,genre as genre1,genre as genre2" - " WHERE playlist.id=playlistitem.playlist AND" - " playlistitem.trackid=tracks.id AND" - " genre1.id=tracks.genre1 AND" - " genre2.id=tracks.genre2 AND" - " %s" - " ORDER BY playlist.title,", - m_restriction.c_str()); - idfield = "playlist.id"; - } - else if (m_level == 2) - { - sprintf(sqlbuff, - "SELECT CONCAT(tracks.artist,' - ',tracks.title)," - " tracks.id" - " FROM playlist,playlistitem,tracks" - " WHERE playlist.id=playlistitem.playlist AND" - " playlistitem.trackid=tracks.id AND" - " %s" - " ORDER BY playlistitem.tracknumber", - m_restriction.c_str()); - idfield = "tracks.id"; - } - else - { - mgWarning("View #%d level %d' not yet implemented", m_view, m_level); - m_expanded = false; - return false; - } - } break; - default: - { - mgError("View '%d' not yet implemented", m_view); - } - } - - // now get all childrean of the current node fromthe database - result = mgSqlReadQuery( &m_db, sqlbuff ); - nrows = mysql_num_rows( result ); - nfields = mysql_num_fields(result); - - numchild = 1; - while( (row = mysql_fetch_row(result) ) != NULL ) - { - // row[0] is the printable label for the new child - // row[1] is the unique id for the new child - sprintf( idbuf, "%s_%03d", m_id.c_str(), numchild ); - - // Zweite ebene zeigt alle Tracks des Albums und nicht nur - // diese die den Filterkriterien entsprechen. - // das betrifft nur die Search Views! - - std::string row0 = mgDB::escape_string( &m_db, std::string( row[0] ) ); - std::string row1 = mgDB::escape_string( &m_db, std::string( row[1] ) ); - - if( m_view < 100 ) - { - new_restriction = m_restriction + " AND " - + idfield + "='" + row1 + "'"; - } - else - { - new_restriction = idfield + "='" + row1 + "'"; - } - - new_child = new GdTreeNode(this, // parent - (std::string) idbuf, // id - // row[0], // label, - row0, - new_restriction); - m_children.push_back(new_child); - numchild++; - } - } - else if (m_view <100) - { - new_child = new GdTreeNode(this, // parent - "1" , // id - tr("Artist -> Album -> Track"), // label, - m_restriction); - m_children.push_back(new_child); - new_child = new GdTreeNode(this, // parent - "2" , // id - tr("Genre -> Artist -> Album -> Track") , // label, - m_restriction); - m_children.push_back(new_child); - new_child = new GdTreeNode(this, // parent - "3" , // id - tr("Artist -> Track") , // label, - m_restriction); - m_children.push_back(new_child); - new_child = new GdTreeNode(this, // parent - "4" , // id - tr("Genre -> Year -> Track") , // label, - m_restriction); - m_children.push_back(new_child); - new_child = new GdTreeNode(this, // parent - "5" , // id - tr("Album -> Track") , // label, - m_restriction); - m_children.push_back(new_child); - } - else - { - new_child = new GdTreeNode(this, // parent - "" , // id - tr("Search Result"), // label, - m_restriction); - m_children.push_back(new_child); - } - - m_expanded = true; - mgDebug(5, "%d children expanded\n", m_children.size()); - return true; -} - -/*! - * \brief iterate all children recursively to find the tracks - */ -std::vector<mgContentItem*>* GdTreeNode::getTracks() -{ - MYSQL_ROW row; - MYSQL_RES *result; - int nrows; - int nfields; - std::vector<mgContentItem*>* retlist; - int trackid; - - retlist = new std::vector<mgContentItem*>(); - - // get all tracks satisying the restrictions of this node - mgDebug(5, "getTracks(): query '%s'", m_restriction.c_str()); - - result = mgSqlReadQuery(&m_db, - "SELECT tracks.id FROM tracks, album, genre WHERE %s" - " AND album.cddbid=tracks.sourceid AND genre.id=tracks.genre1", - m_restriction.c_str()); - nrows = mysql_num_rows(result); - nfields = mysql_num_fields(result); - - while((row = mysql_fetch_row(result)) != NULL) - { - // row[0] is the trackid - if(sscanf(row[0], "%d", &trackid) != 1) - { - mgError("Can not extract integer track id from '%s'", - row[0]); - } - retlist->push_back(new mgGdTrack(trackid, m_db)); - } - return retlist; -} - - -/*! - ***************************************************************************** - * \brief returns the first track matchin the restrictions of this node - * assuming we are in a leaf node, this returns the track represented by the - * the leaf - ****************************************************************************/ -mgContentItem* GdTreeNode::getSingleTrack() -{ - MYSQL_ROW row; - MYSQL_RES *result; - int nrows; - int nfields; - mgContentItem* track = NULL; - int trackid; - - // get all tracks satisying the restrictions of this node - mgDebug(5, "getTracks(): query '%s'", m_restriction.c_str()); - - result = mgSqlReadQuery(&m_db, - "SELECT tracks.id FROM tracks, album, genre WHERE %s" - " AND album.cddbid=tracks.sourceid AND genre.id=tracks.genre1", - m_restriction.c_str()); - nrows = mysql_num_rows(result); - nfields = mysql_num_fields(result); - - if( nrows != 1 ) - { - mgWarning( "GdTreeNode::getSingleTrack() :SQL call returned %d tracks, using only the first", - nrows ); - } - // get the first row - if( ( row = mysql_fetch_row(result)) != NULL ) - { - // row[0] is the trackid - if(sscanf(row[0], "%d", &trackid) != 1) - { - mgError("Can not extract integer track id from '%s'", - row[0]); - } - track = new mgGdTrack(trackid, m_db); - } - return track; -} - diff --git a/gd_content_interface.h b/gd_content_interface.h deleted file mode 100644 index 8823799..0000000 --- a/gd_content_interface.h +++ /dev/null @@ -1,582 +0,0 @@ -/*! - * \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$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author$ - * - * Declares main classes for content items and interfaces to SQL databases - * - * This file defines the following classes - * - gdFilterSets: filters to specifically search for GD items - * - mgGdTrack: a single track (content item). e.g. an mp3 file - * - GdTracklist: - * - GdPlaylist: - * - GdTreeNode: - */ - -#ifndef _GD_CONTENT_INTERFACE_H -#define _GD_CONTENT_INTERFACE_H - -#include <string> -#include <vector> - -#include <mysql/mysql.h> - -#include "mg_content_interface.h" -#include "mg_media.h" -#include "mg_playlist.h" -#include "mg_filters.h" -#include "i18n.h" - -/*! - * \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: - - //\@{ - - /*! - * \brief the default constructor - * - * Constructs a number ( >= 1 ) of filter sets where - * the first (index 0) is active by default. - */ - gdFilterSets(); - - /*! - * \brief the destructor - */ - virtual ~gdFilterSets(); - - //\@ - - /*! - * \brief compute restriction w.r.t active filters - * - * Computes the (e.g. sql) restrictions specified by - * the active filter sets. - * - * \param viewPort - after call, contains the index of the appropriate default view in - * \return sql string representing the restrictions - * \todo should viewPort be a reference? - */ - virtual std::string computeRestriction(int *viewPort); -}; - - -/*! - * \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. - * The actual data is only read when a content field is accessed for - * the first time. For subsequent access, cached values are used. - * - * \todo does each track node need a reference to the database? - * maybe we can use a static db handle in mgDatabase? - */ -class mgGdTrack : public mgContentItem -{ - public: - - //@{ - - /*! - * \brief a constructor - * - * Creates an invalid item. - * - * \todo does this make sense? used anywhere? - */ - mgGdTrack() - { - m_uniqID = -1; - } - - /*! - * \brief a constructor for a specific item - * - * The constructor creates a specific item in a given database - * On creation, the object is only a wrapper without data. Actual - * data fields are filled when readData() is called for the first time. - * - * \param sqlIdentifier - a unique ID of the item which will be represented - * \param dbase - the database in which the item exists - */ - mgGdTrack( int sqlIdentifier, MYSQL dbase ); - - /*! - * \brief a copy constructor - */ - mgGdTrack(const mgGdTrack&); - - /*! - * \brief the destructor - */ - virtual ~mgGdTrack(); - - //@} - - /*! - * \brief obtain the content type of the item - */ - virtual mgContentItem::contentType getContentType() - { - return mgContentItem::GD_AUDIO; - } - - /*! - * \brief obtain the associated player object - * - * \todo what is this used for? - */ - virtual mgMediaPlayer getPlayer() - { - return mgMediaPlayer(); - } - - //\@{ - - /*! - * \brief returns a certain field of the item as a string - * - * 0 - title - * 1 - artist - * 2 - album - * 3 - genre - */ - virtual std::string getLabel( int col = 0 ); - - /*! - * \brief returns value for the track title - */ - virtual std::string getTitle(); - - /*! - * \brief returns value for the location of the track on disk - */ - virtual std::string getSourceFile(); - - /*! - * \brief returns the genre of the track - */ - virtual std::string getGenre(); - - /*! - * \brief returns the artist of the track - */ - std::string getArtist(); - - /*! - * \brief returns value the album to which the track belongs - */ - std::string getAlbum(); - - /*! - * \brief obtain the location of the image file - */ - std::string getImageFile(); - - /*! - * \brief obtain the year of the track - */ - int getYear(); - - /*! - * \brief obtain the duration of the track - */ - int getDuration(); - - /*! - * \brief obtain the rating of the track - */ - 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 bitrate of the track - */ - virtual int getLength(); - - /*! - * \brief obtain the complete track information - */ - virtual std::vector<mgFilter*> *getTrackInfo(); - - //\@} - - //\@{ - /*! - * \brief set the title of the track - */ - void setTitle(std::string new_title); - - /*! - * \brief set the title of the track - */ - void setArtist(std::string new_artist); - - /*! - * \brief set the album name of the track - */ - void setAlbum(std::string new_album); - - /*! - * \brief set the genre of the track - */ - void setGenre(std::string new_genre); - - /*! - * \brief set the year of the track - */ - void setYear(int new_year); - - /*! - * \brief set the rating of the track - */ - void setRating(int new_rating); - - /*! - * \brief set complete information of the track - */ - virtual bool setTrackInfo(std::vector<mgFilter*>*); - - /*! - * \brief make changes persistent - * - * The changes made using the setXxx methods are not - * stored persistently in the database until writeData - * is called. - * - * \note only the tracks table in the SQL database is updated. - * Genre and album field information is lost. - */ - bool writeData(); - //\@} - - //! \brief a special instance denoting an undefined track - static mgGdTrack UNDEFINED; - -private: - - /*! - * \brief the database in which the track resides - */ - MYSQL m_db; - - /*! - * \brief a dirty flag - * - * false, if content field values have not yet been retrieved - * from the database. Set to true when contents are retrieved - * (on demand only). - */ - bool m_retrieved; - - /*! - * \brief the artist name - */ - std::string m_artist; - - /*! - * \brief the track title - */ - std::string m_title; - - /*! - * \brief the filename - */ - std::string m_mp3file; - - /*! - * \brief The album to which the file belongs - */ - std::string m_album; - - /*! - * \brief The genre of the music - */ - 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; - - /*! - * \brief The rating by the user - */ - int m_rating; - - /*! - * \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 - * of the mgGdTrack object may not be filled upon creation. As soon as the - * first content field is needed, this private function is called to fill - * all content fields at once. - */ - bool readData(); - -}; - -/* - * \brief a list of tracks stored in the databas - * \ingroup giantdisc - */ -class GdTracklist : public mgTracklist -{ - public: - - GdTracklist(MYSQL db_handle, std::string restrictions); -}; - -/*! - * \class GdPlaylist - * - * \brief represents a playlist, i.e. an ordered collection of tracks - */ -class GdPlaylist : public mgPlaylist -{ - public: - - //@{ - /*! - * \brief opens an existing or creates empty playlist by name - * - * If the playlist does not yet exist, an empty playlist is created. - * - * \param listname - user-readable identifier of the paylist - * \param db_handle - database which stores the playlist - */ - GdPlaylist(std::string listname, MYSQL db_handle); - - /*! - * \brief destructor - * - * \note the destructor only destroys the in-memory footprint of the playlist, - * it does not modify the database. - */ - virtual ~GdPlaylist(); - - //@} - - /*! - * changes the listname of the playlist (and unset the sql id) - */ - virtual void setListname(std::string name); - - /*! - * returns the total duration of all songs in the list in seconds - */ - int getPlayTime(); - - /*! - * returns the duration of all remaining songs in the list in seconds - */ - int getPlayTimeRemaining(); - - /*! - * \brief write data back to the database - */ - bool storePlaylist(); - - /*! - * \brief store playlist under a different name - */ - bool storeAs( std::string name ); - - private: - - /*! - * \brief reads the track list from the sql database into memory - */ - int insertDataFromSQL(); - - //! \brief the database identifier of the list - int m_sqlId; - - //! \brief the list type used in GiantDisc db queries - int m_listtype; - - //! \brief the author of the playlist - std::string m_author; - - //! \brief the handle to the database in which the list is stored - MYSQL m_db; -}; - -/*! - * \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 - * The levels hof the hierarchy can be expanded dynamically by specifying - * the database field for the next expansion step - * In this way, the expnasion scheme (order of the fields) is not static. - * When a node is expanded, a list of children is created. - * Each child inherits the restrictions of its father and an additional - * restriction on the recently expanded db field - */ -class GdTreeNode : public mgSelectionTreeNode -{ -public: - - //@{ - /*! - * \brief Construct a top level tree node in a certain view with certain filters - */ - GdTreeNode(MYSQL db, int view, std::string filters); - - /*! - * \brief Construct a subordinate tree node with given labels and restrictions - */ - GdTreeNode(mgSelectionTreeNode* parent, - std::string id, std::string label, std::string restriction); - - /*! - * \brief destructor to clean up the object - */ - virtual ~GdTreeNode(); - //@} - - //@{ - /*! - * \brief check, whether the node is a leaf node (i.e. has no more children) - */ - virtual bool isLeafNode(); - - /*! - * \brief expand the node and generate child nodes according to restrictions passed in the constructor - */ - virtual bool expand(); - - /*! - * \brief obtain the tracks in this node - */ - virtual std::vector<mgContentItem*>* getTracks(); - - /*! - * \brief obtain a single track in this node - */ - virtual mgContentItem* getSingleTrack(); - - //@} -}; - -/* -------------------- begin CVS log --------------------------------- - * $Log: gd_content_interface.h,v $ - * Revision 1.11 2004/08/30 14:31:43 LarsAC - * Documentation added - * - * Revision 1.10 2004/08/27 15:19:34 LarsAC - * Changed formatting and documentation - * - * Revision 1.9 2004/07/29 06:17:50 lvw - * Added todo entries - * - * Revision 1.8 2004/07/06 00:20:51 MountainMan - * loading and saving playlists - * - * Revision 1.7 2004/05/28 15:29:18 lvw - * Merged player branch back on HEAD branch. - * - * - * Revision 1.6 2004/02/23 15:41:21 RaK - * - first i18n attempt - * - * Revision 1.5 2004/02/12 09:15:07 LarsAC - * Moved filter classes into separate files - * - * Revision 1.4.2.6 2004/05/25 00:10:45 lvw - * Code cleanup and added use of real database source files - * - * Revision 1.4.2.5 2004/04/01 21:35:32 lvw - * Minor corrections, some debugging aid. - * - * Revision 1.4.2.4 2004/03/14 17:57:30 lvw - * Linked against libmad. Introduced config options into code. - * - * Revision 1.4.2.3 2004/03/10 13:11:24 lvw - * Added documentation - * - * Revision 1.4.2.2 2004/03/08 07:14:27 lvw - * Preliminary changes to muggle player - * - * Revision 1.4.2.1 2004/03/02 07:05:50 lvw - * Initial adaptations from MP3 plugin added (untested) - * - * Revision 1.6 2004/02/23 15:41:21 RaK - * - first i18n attempt - * - * Revision 1.5 2004/02/12 09:15:07 LarsAC - * Moved filter classes into separate files - * - * Revision 1.4 2004/02/09 19:27:52 MountainMan - * filter set implemented - * - * Revision 1.3 2004/02/02 22:48:04 MountainMan - * added CVS $Log - * - * - * --------------------- end CVS log ---------------------------------- - */ -#endif /* END _GD_CONTENT_INTERFACE_H */ - - - @@ -9,839 +9,1130 @@ #include "i18n.h" -const tI18nPhrase Phrases[] = { +const tI18nPhrase Phrases[] = +{ - { "items", - "Einträge", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Notation",// Traduction en Français Patrice Staudt - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Muggle Media Database", - "Muggle Media Database", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Base de donnée Muggle Media",// TODO Francais - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Tree View Commands", - "Browser Befehle", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Commande navigateur",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Search Result", - "Suchergebnis", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Résultat de recherche",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Album -> Track", - "Titel nach Album", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Titre après albume",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Genre -> Year -> Track", - "Titel nach Genre und Jahr", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Genre -> Année -> Titre",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Artist -> Track", - "Titel nach Interpret", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "interprète -> titre",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Genre -> Artist -> Album -> Track", - "Album nach Genre und Interpret", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Genre -> interprète -> album -> Titre",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Artist -> Album -> Track", - "Album nach Interpret", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "interprète -> album -> Titre",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Browser", - "Browser", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Navigateur",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Other Search", - "Suchmodus", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Mode rechercher",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Query", - "Suche", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Rechercher",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Album info", - "Album Details", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Infos Albume",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Edit PL?", - "Edit PL?", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Édition PL?",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Filter", - "Filter", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Filtre",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Track info", - "Titel Details", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Détails trites",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Edit PL", - "Edit PL", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Édition PL",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Track Search", - "Titel Suche", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Chercher titre",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Album Search", - "Album Suche", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Chercher albume",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Playlist Search", - "Playlist Suche", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Chercher playlist",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "playlist title", - "Playlist Titel", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Playlist titre",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "playlist author", - "Playlist Author", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Playlist auteur",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "album artist", - "Albuminterpret", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Albume interpret",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "album title", - "Albumtitel", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Albume titre",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "rating", - "Bewertung", - "",// TODO - "",// TODO - "",// TODO - "estimation",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "year (to)", - "Jahr (bis)", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Année (à)",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "year (from)", - "Jahr (von)", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Année (de)",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "genre", - "Genre", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Genre",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "artist", - "Interpret", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "interprète",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "title", - "Titel", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "titre",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Add", - "Hinzu", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "ajouter",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Cycle tree", - "Browser Modus", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Mode Navigateur",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Playlist", - "Playliste", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Playlist",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Submenu", - "Untermenü", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "sous menu",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Load", - "Laden", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "charger",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Save", - "Speichern", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "sauvegarder",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Clear", - "Loeschen", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "effacer",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Mainmenu", - "Hauptmenue", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Menu principal",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "%d tracks sent to current playlist", - "%d Titel in aktuelle Playlist?", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "%d mettre titre dans la playlist actuel?",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Load playlist", - "Playliste laden", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Charger playlist",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Save playlist", - "Playliste speichern", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "sauvegarder playlist",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Rename playlist", - "Playliste umbenennen", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Playliste umbenennen",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Clear playlist", - "Playliste leeren", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Playliste vider",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Remove entry from list", - "Eintrag aus der Playliste entfernen", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Supprimer de la list",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Export playlist", - "Playliste exportieren", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Exporter la playlist",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "External playlist commands", - "Externe Playlist-Kommandos", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "commande externe playlist",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Loop mode off", - "Endlosmodus aus", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Déclancher le mode répétition",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Loop mode single", - "Endlosmodus Einzeltitel", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Mode répétition titre seul",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Loop mode full", - "Endlosmodus Playliste", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Mode répétition playlist",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Shuffle mode off", - "Zufallssmodus aus", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "mode allèatoire déclenché",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Shuffle mode normal", - "Zufallssmodus normal", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Mode allèatoire normal",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { "Shuffle mode party", - "Zufallssmodus Party", - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "Mode allèatoire fêtes",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - "",// TODO - }, - { NULL } - }; + { + "Search", + "Suchen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Chercher", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Collections", + "Sammlungen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Collections", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Create collection", + "Sammlung neu anlegen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Créer une nouvelle collection", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Delete collection", + "Sammlung löschen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Effacer la collection", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Delete collection '%s'", + "Sammlung '%s' löschen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Effacer la collection '%s'", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Collections", + "Sammlungen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Collections", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Commands", + "Befehle", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Commandes", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Commands:%s", + "Befehle:%s", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Commandes:%s", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Search Result", + "Suchergebnis", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Résultat de recherche", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Album -> Track", + "Titel nach Album", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Titre après albume", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Genre -> Year -> Track", + "Titel nach Genre und Jahr", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Genre -> Année -> Titre", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Artist -> Track", + "Titel nach Interpret", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "interprète -> titre", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Genre -> Artist -> Album -> Track", + "Album nach Genre und Interpret", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Genre -> interprète -> album -> Titre", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Artist -> Album -> Track", + "Album nach Interpret", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "interprète -> album -> Titre", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Collection", + "Sammlung", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Collection", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "List", + "Liste", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Liste", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Export track list", + "Stückliste exportieren", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Exporter la liste", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "External playlist commands", + "Externe Playlist-Kommandos", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "commande externe playlist", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Loop mode off", + "Endlosmodus aus", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Déclancher le mode répétition", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Loop mode single", + "Endlosmodus Einzeltitel", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Mode répétition titre seul", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Loop mode full", + "Endlosmodus alle", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Mode répétition playlist", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Shuffle mode off", + "Zufallsmodus aus", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "mode allèatoire déclenché", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Shuffle mode normal", + "Zufallsmodus normal", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Mode allèatoire normal", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Artist", + "Interpret", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Interprète", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Play all", + "Spiele alles", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Jouer tout", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Set", + "Setzen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Définir", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Instant play", + "Sofort spielen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Jouer en direct", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Instant play '%s'", + "'%s' sofort spielen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Jouer '%s' en direct", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Shuffle mode party", + "Zufallsmodus Party", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Mode allèatoire fêtes", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Default", + "Ziel", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Destinataire", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Set default to collection '%s'", + "Setze Ziel auf Sammlung '%s'", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Changer destination à la collection '%s'", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Default collection now is '%s'", + "Zielsammlung ist nun '%s'", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "La collection destinataire est maintenant '%s'", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Add", + "Hinzu", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Ajouter", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Add to '%s'", + "Zu '%s' hinzufügen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Ajouter à '%s'", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Add '%s' to '%s'", + "'%s' zu '%s' hinzufügen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Ajouter '%s' à '%s'", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Add all to '%s'", + "Alles '%s' hinzufügen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Ajouter tout à '%s'", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Remove", + "Entfernen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Effacer", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Remove from '%s'", + "Aus '%s' entfernen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Effacer de '%s'", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Remove '%s' from '%s'", + "'%s' aus '%s' entfernen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Effacer '%s' de '%s'", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Remove all entries from '%s'", + "Alle Einträge aus '%s' entfernen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Effacer tout de '%s'", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Remove all from '%s'", + "Alles aus '%s' entfernen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Effacer tout de '%s'", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "New collection", + "Neue Sammlung anlegen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Ajouter une collection", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Remove this collection", + "Diese Sammlung entfernen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Effacer cette collection", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Remove entry from this collection", + "Eintrag aus dieser Sammlung entfernen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Effacer de cette collection", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Added %s entries", + "%s Einträge hinzugefügt", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Ajouté %s pièces", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Removed %s entries", + "%s Einträge entfernt", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Effacé %s pièces", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Removed all entries", + "Alle Einträge entfernt", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Effacé toutes les pièces", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Now playing", + "Jetzt wird gespielt", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "En jouant", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Rating", + "Bewertung", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Decade", + "Dekade", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Décade", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Year", + "Jahr", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Année", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Album", + "Album", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Album", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Genre 1", + "Genre 1", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Genre 1", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Genre 2", + "Genre 2", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Genre 2", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Title", + "Titel", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Titre", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Track", + "Track", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Pièce", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Tree View Selection", + "Suchschema wählen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Choisir le schéma de recherche", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Title -> Album -> Track", + "Titel -> Album -> Track", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Titre -> Album -> Pièce", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Collection -> Item", + "Sammlung - Stück", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Collectin -> Pièce", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Genre -> Decade -> Artist -> Album -> Track", + "Genre -> Dekade -> Interpret -> Album -> Track", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Genre -> Décade -> Interprète -> Album -> Pièce", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Search", + "Suche", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Chercher", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "play", + "spielen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "jouer", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Collection item", + "Sammlungseintrag", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Pièce de collection", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Collection '%s' NOT deleted", + "Sammlung '%s' NICHT gelöscht", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Collection '%s' PAS effacée", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Collection '%s' deleted", + "Sammlung '%s' gelöscht", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Collection '%s' effacée", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + { + "Select search order", + "Suchschema wählen", + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "Choisir le schéma de recherchage", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + "", // TODO + }, + {NULL} +}; @@ -13,5 +13,4 @@ #include <i18n.h> extern const tI18nPhrase Phrases[]; - -#endif //_I18N__H +#endif //_I18N__H diff --git a/mg_actions.c b/mg_actions.c new file mode 100644 index 0000000..d9e45af --- /dev/null +++ b/mg_actions.c @@ -0,0 +1,986 @@ +/*! + * \file mg_actions.c + * \brief Implements all actions for browsing media libraries within VDR + * + * \version $Revision: 1.27 $ * \date $Date: 2004-12-25 16:52:35 +0100 (Sat, 25 Dec 2004) $ + * \author Wolfgang Rohdewald + * \author Responsible author: $Author: wr61 $ + * + * $Id: mg_actions.c 276 2004-12-25 15:52:35Z wr61 $ + */ + +#include <typeinfo> +#include <string> +#include <vector> + +#include <menuitems.h> +#include <tools.h> +#include <plugin.h> + +#include "vdr_setup.h" +#include "mg_actions.h" +#include "vdr_menu.h" +#include "i18n.h" +#include <vdr/interface.h> + +#define DEBUG +#include "mg_tools.h" + +void +mgAction::Notify() +{ + m->SetHelpKeys(); +} + +void +mgAction::SetMenu(mgMenu *menu) +{ + m = menu; +} + +eOSState +mgAction::Back() +{ + return osUnknown; +} + +bool +mgAction::Enabled() +{ + return true; +} + +mgAction::mgAction() +{ + m = NULL; +} + +mgAction::~mgAction() +{ +} + + +//! \brief used for normal data base items +class mgEntry : public mgOsdItem +{ + public: + void Notify(); + bool Enabled() { return true;} + const char *ButtonName() { return ""; } + const char *MenuName (const unsigned int idx,const string value); + eOSState ProcessKey(eKeys key); + void Execute(); + eOSState Back(); +}; + +class mgCommand : public mgOsdItem +{ + public: + virtual eOSState ProcessKey(eKeys key); + void Execute(); +}; + + +mgSelection* +mgAction::playselection () +{ + return m->playselection (); +} +mgMainMenu* +mgAction::osd () +{ + return m->osd (); +} + +eOSState +mgOsdItem::Back() +{ + osd()->newmenu = NULL; + return osContinue; +} + + +void +mgEntry::Notify() +{ + selection()->setPosition(osd()->Current()); + selection()->gotoPosition(); + osd()->SaveState(); + mgAction::Notify(); +} + +const char * +mgEntry::MenuName(const unsigned int idx,const string value) +{ + char *result; + if (selection()->isCollectionlist()) + { + if (value == osd()->default_collection) + asprintf(&result,"-> %s",value.c_str()); + else + asprintf(&result," %s",value.c_str()); + } + else if (selection()->inCollection()) + asprintf(&result,"%4d %s",idx,value.c_str()); + else + result = strdup(value.c_str()); + return result; +} + +void +mgEntry::Execute() +{ + if (selection ()->enter ()) + { + osd()->forcerefresh = true; + osd()->m_Status->IgnoreNextEventOn = this; + } + else + { + m->ExecuteAction(actInstantPlay); + } +} + +eOSState +mgEntry::ProcessKey(eKeys key) +{ + if (key!=kNone) + mgDebug(3,"mgEntry(%s):ProcessKey(%d)",Text(),(int)key); + + switch (key) { + case kOk: + Execute(); + return osContinue; + case kBack: + osd()->m_Status->IgnoreNextEventOn = this; + return Back(); + case kBlue: + osd ()->newmenu = new mgSubmenu; + return osContinue; + default: + return osUnknown; + } +} + + +eOSState +mgEntry::Back() +{ + osd()->forcerefresh = true; + if (!selection ()->leave ()) + osd()->newmenu = NULL; + return osContinue; +} + +eOSState +mgCommand::ProcessKey(eKeys key) +{ + if (key!=kNone) + mgDebug(3,"mgCommand::ProcessKey(%d)",(int)key); + mgMenu *parent = osd ()->Parent (); + mgMenu *n = osd ()->newmenu; + osd ()->newmenu = NULL; + eOSState result = osContinue; + switch (key) + { + case kRed: + if (osd()->UsingCollection) + parent->CollRedAction = Type(); + else + parent->TreeRedAction = Type(); + break; + case kGreen: + if (osd()->UsingCollection) + parent->CollGreenAction = Type(); + else + parent->TreeGreenAction = Type(); + break; + case kYellow: + if (osd()->UsingCollection) + parent->CollYellowAction = Type(); + else + parent->TreeYellowAction = Type(); + break; + case kBlue: + case kBack: + break; + case kOk: + Execute (); + break; + default: + osd ()->newmenu = n; // wrong key: stay in submenu + result = osUnknown; + break; + } + return result; +} + +void +mgCommand::Execute() +{ +} + +class mgExternal : public mgCommand +{ + public: + const char *ButtonName(); + const char *MenuName (const unsigned int idx,const string value); + void Execute(); + private: + cCommand * Command(); +}; + +class mgExternal0 : public mgExternal { }; +class mgExternal1 : public mgExternal { }; +class mgExternal2 : public mgExternal { }; +class mgExternal3 : public mgExternal { }; +class mgExternal4 : public mgExternal { }; +class mgExternal5 : public mgExternal { }; +class mgExternal6 : public mgExternal { }; +class mgExternal7 : public mgExternal { }; +class mgExternal8 : public mgExternal { }; +class mgExternal9 : public mgExternal { }; +class mgExternal10 : public mgExternal { }; +class mgExternal11 : public mgExternal { }; +class mgExternal12 : public mgExternal { }; +class mgExternal13 : public mgExternal { }; +class mgExternal14 : public mgExternal { }; +class mgExternal15 : public mgExternal { }; +class mgExternal16 : public mgExternal { }; +class mgExternal17 : public mgExternal { }; +class mgExternal18 : public mgExternal { }; +class mgExternal19 : public mgExternal { }; + +const char* +mgExternal::ButtonName() +{ + cCommand *command = Command(); + if (command) + { + return command->Title(); + } + else + return ""; +} + +const char* +mgExternal::MenuName(const unsigned int idx,const string value) +{ + return strdup(ButtonName()); +} + +cCommand * +mgExternal::Command() +{ + cCommand *command = NULL; + if (osd()->external_commands) + { + unsigned int idx = Type() - actExternal0; + command = osd()->external_commands->Get (idx); + } + return command; +} + +void +mgExternal::Execute() +{ + cCommand *command = Command(); + if (command) + { + mgDebug(1,"external command:%s",command->Title()); + bool confirmed = true; + if (command->Confirm ()) + { + char *buffer; + asprintf (&buffer, "%s?", command->Title ()); + confirmed = Interface->Confirm (buffer); + free (buffer); + } + if (confirmed) + { + osd()->Message1 ("%s...", command->Title ()); + selection ()->select (); + string m3u_file = selection ()->exportM3U (); + selection ()->leave (); + if (!m3u_file.empty ()) + { + /*char *result = (char *)*/ + command->Execute (m3u_file.c_str ()); +/* What to do? Recode cMenuText (not much)? + if (result) + { + free( result ); + return AddSubMenu( new cMenuText( command->Title(), result ) ); + } +*/ + } + } + } +} + +//! \brief select search order +class mgChooseSearch : public mgCommand +{ + public: + bool Enabled(); + eOSState ProcessKey(eKeys key); + void Execute (); + const char *ButtonName() { return tr("Search"); } + const char *MenuName(const unsigned int idx,const string value) + { return strdup(tr("Select search order")); } +}; + +bool +mgChooseSearch::Enabled() +{ + bool result = mgOsdItem::Enabled(); + result &= (!selection()->isCollectionlist()); + return result; +} + +eOSState +mgChooseSearch::ProcessKey(eKeys key) +{ + if (key!=kNone) + mgDebug(3,"mgChooseSearch::ProcessKey(%d)",int(key)); + if (key == kOk) + { + osd()->Menus.pop_back(); + Execute(); + return osContinue; + } + else + return mgCommand::ProcessKey(key); +} + +void mgChooseSearch::Execute() +{ + osd ()->newmenu = new mgTreeViewSelector; + +} + +//! \brief toggles between the normal and the collection selection +class mgToggleSelection:public mgCommand +{ + public: + void Execute (); + const char *ButtonName (); + const char *MenuName (const unsigned int idx,const string value); +}; + +const char * +mgToggleSelection::ButtonName () +{ + if (osd ()->UsingCollection) + return tr ("Search"); + else + return tr ("Collections"); +} + + +const char * +mgToggleSelection::MenuName (const unsigned int idx,const string value) +{ + if (osd ()->UsingCollection) + return strdup(tr ("Search")); + else + return strdup(tr ("Collections")); +} + + +void +mgToggleSelection::Execute () +{ + if (osd ()->UsingCollection) + osd ()->UseNormalSelection (); + else + { + osd ()->UseCollectionSelection (); + selection()->clearCache(); + } + osd()->newposition = selection ()->gotoPosition (); +} + + +//! \brief sets the default collection selection +class mgSetDefault:public mgCommand +{ + public: + bool Enabled(); + void Execute (); + const char *ButtonName () + { + return tr ("Default"); + } + const char *MenuName (const unsigned int idx,const string value); +}; + +const char * mgSetDefault::MenuName(const unsigned int idx,const string value) +{ + char *b; + asprintf (&b, tr("Set default to collection '%s'"), + selection ()->getCurrentValue().c_str()); + return b; +} + +class mgSetButton : public mgCommand +{ + const char *ButtonName() + { + return tr("Set"); + } + const char *MenuName(const unsigned int idx,const string value) { return strdup(""); } +}; + + +bool +mgSetDefault::Enabled() +{ + bool result = mgOsdItem::Enabled(); + result &= (!osd()->DefaultCollectionSelected()); + result &= osd()->UsingCollection; + result &= (selection ()->level () == 0); + return result; +} + +void +mgSetDefault::Execute () +{ + if (!Enabled()) + mgError("mgSetDefault not enabled"); + osd ()->default_collection = selection ()->getCurrentValue(); + osd()->Message1 ("Default collection now is '%s'", + osd ()->default_collection); +} + + +//! \brief instant play +class mgInstantPlay : public mgCommand { + public: + void Execute (); + const char *ButtonName () + { + return tr ("Instant play"); + } + const char *MenuName (const unsigned int idx,const string value); +}; + +const char * +mgInstantPlay::MenuName (const unsigned int idx,const string value) +{ + return strdup(tr("Instant play")); +} + +void +mgInstantPlay::Execute() +{ + osd()->PlayInstant(true); +} + +//! \brief add selected items to default collection +class mgAddAllToCollection:public mgCommand { + public: + void Execute (); + //! \brief adds the whole selection to a collection + // \param collection the target collection. Default is the default collection + void ExecuteSelection (mgSelection *s,const string collection=""); + const char *ButtonName () + { + return tr ("Add"); + } + const char *MenuName (const unsigned int idx,const string value); +}; + +const char * +mgAddAllToCollection::MenuName (const unsigned int idx,const string value) +{ + char *b; + asprintf (&b, tr ("Add all to '%s'"), + osd ()->default_collection.c_str ()); + return b; +} + +void +mgAddAllToCollection::Execute() +{ + ExecuteSelection(selection()); +} + +void +mgAddAllToCollection::ExecuteSelection (mgSelection *s, const string collection) +{ + string target = collection; + if (target.empty()) + target = osd()->default_collection; + if (target == osd()->play_collection) + if (!PlayerControl()) + collselection()->ClearCollection(target); + + osd()->Message1 ("Added %s entries",itos (s->AddToCollection (target))); + + if (target == osd()->play_collection) + { + playselection()->clearCache(); + mgPlayerControl *c = PlayerControl(); + if (c) + c->ReloadPlaylist(); + else + osd()->PlayQueue(); + } +} + +//! \brief add selected items to default collection +class mgAddThisToCollection:public mgAddAllToCollection +{ + public: + bool Enabled(); + void Execute (); + const char *ButtonName (); + const char *MenuName (const unsigned int idx,const string value); +}; + + +void +mgAddThisToCollection::Execute () +{ +// work on a copy, so we don't have to clear the cache of selection() +// which would result in an osd()->forcerefresh which could scroll. + mgSelection *s = new mgSelection(selection()); + s->select (); + mgAddAllToCollection::ExecuteSelection(s); + delete s; +} + +class mgSearchNew : public mgCommand +{ + virtual void NewSearch() = 0; + void Execute(); +}; + +void +mgSearchNew::Execute() +{ + mgSelection *oldsel = osd()->selection(); + map<string,string>* oldkeys = oldsel->UsedKeyValues(); + osd()->UseNormalSelection(); // Default for all searches + NewSearch(); + mgSelection *newsel = osd()->selection(); + for (unsigned int idx = 0; idx < newsel->size(); idx++) + { + string new_keyname = newsel->getKeyChoice(idx); + if (oldkeys->count(new_keyname)==0) break; + newsel->select((*oldkeys)[new_keyname]); + } + newsel->leave(); + delete oldkeys; +} + +class mgSearchCollItem : public mgSearchNew +{ + const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Collection -> Item")); } + void NewSearch() { osd ()->UseCollectionSelection (); } +}; + + +const char * +mgAddThisToCollection::ButtonName () +{ + return tr("Add"); +} + +bool +mgAddThisToCollection::Enabled() +{ + bool result = mgOsdItem::Enabled(); + result &= (!osd()->DefaultCollectionSelected()); + return result; +} + +const char * +mgAddThisToCollection::MenuName (const unsigned int idx,const string value) +{ + char *b; + asprintf (&b, tr ("Add to '%s'"), + osd ()->default_collection.c_str ()); + return b; +} + +//! \brief remove selected items from default collection +class mgRemoveAllFromCollection:public mgCommand +{ + public: + void Execute (); + const char *ButtonName () + { + return tr ("Remove"); + } + const char *MenuName (const unsigned int idx,const string value); +}; + +void +mgRemoveAllFromCollection::Execute () +{ + osd()->Message1 ("Removed %s entries", + itos (collselection ()->RemoveFromCollection ( + osd ()->default_collection.c_str ()))); + if (osd()->default_collection == osd()->play_collection) + { + mgPlayerControl *c = PlayerControl(); + if (c) + c->ReloadPlaylist(); + } +} + + +const char * +mgRemoveAllFromCollection::MenuName (const unsigned int idx,const string value) +{ + char *b; + asprintf (&b, tr ("Remove all from '%s'"), + osd ()->default_collection.c_str ()); + return b; +} + +//! \brief remove selected items from default collection +class mgRemoveThisFromCollection:public mgRemoveAllFromCollection +{ + public: + void Execute (); + const char *ButtonName () + { + return tr ("Remove"); + } + const char *MenuName (const unsigned int idx,const string value); +}; + +void +mgRemoveThisFromCollection::Execute () +{ + if (osd()->DefaultCollectionSelected()) + { + selection()->ClearCollection(selection()->getCurrentValue().c_str()); + osd()->Message ("Removed all entries"); + } + else + { + selection ()->select (); + osd()->Message1 ("Removed %s entries", + itos (selection ()->RemoveFromCollection (osd ()->default_collection.c_str ()))); + selection ()->leave (); + if (osd()->DefaultCollectionEntered()) + { + selection()->clearCache(); + osd()->forcerefresh = true; + } + } +} + + +const char * +mgRemoveThisFromCollection::MenuName (const unsigned int idx,const string value) +{ + char *b; + string this_sel = selection()->getCurrentValue(); + if (osd()->DefaultCollectionSelected()) + asprintf(&b,tr("Remove all entries from '%s'"),this_sel.c_str()); + else + asprintf (&b, tr ("Remove from '%s'"), + trim(osd ()->default_collection).c_str ()); + return b; +} + + +void +mgCreateCollection::Notify() +{ + if (!strchr(Text(),'[')) + if (!strchr(Text(),']')) + osd()->SetHelpKeys(NULL,NULL,NULL,NULL); +} + +const char* +mgCreateCollection::MenuName(const unsigned int idx,const string value) +{ + return strdup(tr ("Create collection")); +} + +mgCreateCollection::mgCreateCollection() : cMenuEditStrItem(MenuName(),value,30,tr(FileNameChars)), mgAction() +{ + value[0]=0; +} + +eOSState +mgCreateCollection::ProcessKey(eKeys key) +{ + if (key == kOk) + Execute(); + return cMenuEditStrItem::ProcessKey(key); +} + +bool +mgCreateCollection::Enabled() +{ + bool result = mgAction::Enabled(); + result &= selection()->isCollectionlist(); + return result; +} + +void +mgCreateCollection::Execute () +{ + string name = trim(value); + if (name.empty()) return; + bool created = selection ()->CreateCollection (name); + if (created) + { + mgDebug(1,"created collection %s",name.c_str()); + selection ()->clearCache(); + osd()->forcerefresh = true; + } + else + osd()->Message1 ("Collection '%s' NOT created", name); +} + +//! \brief delete collection +class mgDeleteCollection:public mgCommand +{ + public: + void Execute (); + bool Enabled(); + const char *ButtonName () + { + return tr ("Delete"); + } + const char *MenuName (const unsigned int idx,const string value); +}; + +bool +mgDeleteCollection::Enabled() +{ + bool result = mgOsdItem::Enabled(); + result &= selection()->isCollectionlist(); + result &= (!osd()->DefaultCollectionSelected()); + if (result) + { + string name = selection ()->getCurrentValue(); + result &= (name != osd()->play_collection); + } + return result; +} + +const char* mgDeleteCollection::MenuName(const unsigned int idx,const string value) +{ + return strdup(tr("Delete collection")); +} + + +void +mgDeleteCollection::Execute () +{ + string name = selection ()->getCurrentValue(); + if (selection ()->DeleteCollection (name)) + { + osd()->Message1 ("Collection '%s' deleted", name); + mgDebug(1,"Deleted collection %s",name.c_str()); + selection ()->clearCache(); + osd()->forcerefresh = true; + } + else + osd()->Message1 ("Collection '%s' NOT deleted", name); +} + + +//! \brief export track list for all selected items +class mgExportTracklist:public mgCommand +{ + public: + void Execute (); + const char *ButtonName () + { + return tr ("Export"); + } + const char *MenuName (const unsigned int idx,const string value) + { + return strdup(tr ("Export track list")); + } +}; + +void +mgExportTracklist::Execute () +{ + selection ()->select (); + string m3u_file = selection ()->exportM3U (); + selection ()->leave (); + osd()->Message1 ("written to %s", m3u_file); +} + +class mgSearchArtistAlbumTitle : public mgSearchNew +{ + const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Artist -> Album -> Title")); } + void NewSearch() + { + selection ()->setKey (0, tr ("Artist")); + selection ()->setKey (1, tr ("Album")); + selection ()->setKey (2, tr ("Title")); + } +}; + + +class mgSearchAlbumTitle : public mgSearchNew +{ + const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Album -> Title")); } + void NewSearch() + { + selection ()->setKey (0, tr ("Album")); + selection ()->setKey (1, tr ("Title")); + } +}; + + +class mgSearchGenreYearTitle : public mgSearchNew +{ + const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Genre 1 -> Year -> Title")); } + void NewSearch() + { + selection ()->setKey (0, tr ("Genre 1")); + selection ()->setKey (1, tr ("Year")); + selection ()->setKey (2, tr ("Title")); + } +}; + + +class mgSearchGenreArtistAlbumTitle : public mgSearchNew +{ + const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Genre 1 -> Artist ->Album -> Title")); } + void NewSearch() + { + selection ()->setKey (0, tr ("Genre 1")); + selection ()->setKey (1, tr ("Artist")); + selection ()->setKey (2, tr ("Album")); + selection ()->setKey (3, tr ("Title")); + } +}; + + +class mgSearchArtistTitle : public mgSearchNew +{ + const char *MenuName(const unsigned int idx,const string value) { return strdup(tr("Artist -> Title")); } + void NewSearch() + { + selection ()->setKey (0, tr ("Artist")); + selection ()->setKey (1, tr ("Title")); + } +}; + + +mgActions +mgOsdItem::Type() +{ + const type_info& t = typeid(*this); + if (t == typeid(mgChooseSearch)) return actChooseSearch; + if (t == typeid(mgToggleSelection)) return actToggleSelection; + if (t == typeid(mgSetDefault)) return actSetDefault; + if (t == typeid(mgInstantPlay)) return actInstantPlay; + if (t == typeid(mgAddAllToCollection)) return actAddAllToCollection; + if (t == typeid(mgRemoveAllFromCollection)) return actRemoveAllFromCollection; + if (t == typeid(mgDeleteCollection)) return actDeleteCollection; + if (t == typeid(mgExportTracklist)) return actExportTracklist; + if (t == typeid(mgAddThisToCollection)) return actAddThisToCollection; + if (t == typeid(mgRemoveThisFromCollection)) return actRemoveThisFromCollection; + if (t == typeid(mgEntry)) return actEntry; + if (t == typeid(mgSetButton)) return actSetButton; + if (t == typeid(mgSearchCollItem)) return actSearchCollItem; + if (t == typeid(mgSearchArtistAlbumTitle)) return actSearchArtistAlbumTitle; + if (t == typeid(mgSearchArtistTitle)) return actSearchArtistTitle; + if (t == typeid(mgSearchAlbumTitle)) return actSearchAlbumTitle; + if (t == typeid(mgSearchGenreYearTitle)) return actSearchGenreYearTitle; + if (t == typeid(mgSearchGenreArtistAlbumTitle)) return actSearchArtistAlbumTitle; + if (t == typeid(mgExternal0)) return actExternal0; + if (t == typeid(mgExternal1)) return actExternal1; + if (t == typeid(mgExternal2)) return actExternal2; + if (t == typeid(mgExternal3)) return actExternal3; + if (t == typeid(mgExternal4)) return actExternal4; + if (t == typeid(mgExternal5)) return actExternal5; + if (t == typeid(mgExternal6)) return actExternal6; + if (t == typeid(mgExternal7)) return actExternal7; + if (t == typeid(mgExternal8)) return actExternal8; + if (t == typeid(mgExternal9)) return actExternal9; + if (t == typeid(mgExternal10)) return actExternal10; + if (t == typeid(mgExternal11)) return actExternal11; + if (t == typeid(mgExternal12)) return actExternal12; + if (t == typeid(mgExternal13)) return actExternal13; + if (t == typeid(mgExternal14)) return actExternal14; + if (t == typeid(mgExternal15)) return actExternal15; + if (t == typeid(mgExternal16)) return actExternal16; + if (t == typeid(mgExternal17)) return actExternal17; + if (t == typeid(mgExternal18)) return actExternal18; + if (t == typeid(mgExternal19)) return actExternal19; + mgError("Unknown mgOsdItem %s",t.name()); + return mgActions(0); +} + +mgOsdItem* +actGenerate(const mgActions action) +{ + mgOsdItem * result = NULL; + switch (action) + { + case actChooseSearch: result = new mgChooseSearch;break; + case actToggleSelection: result = new mgToggleSelection;break; + case actSetDefault: result = new mgSetDefault;break; + case actUnused1: break; + case actInstantPlay: result = new mgInstantPlay;break; + case actAddAllToCollection: result = new mgAddAllToCollection;break; + case actRemoveAllFromCollection:result = new mgRemoveAllFromCollection;break; + case actDeleteCollection: result = new mgDeleteCollection;break; + case actExportTracklist: result = new mgExportTracklist;break; + case actUnused2: break; + case actUnused3: break; + case actAddThisToCollection: result = new mgAddThisToCollection;break; + case actRemoveThisFromCollection: result = new mgRemoveThisFromCollection;break; + case actEntry: result = new mgEntry;break; + case actSetButton: result = new mgSetButton;break; + case actSearchCollItem: result = new mgSearchCollItem;break; + case actSearchArtistAlbumTitle: result = new mgSearchArtistAlbumTitle;break; + case actSearchArtistTitle: result = new mgSearchArtistTitle;break; + case actSearchAlbumTitle: result = new mgSearchAlbumTitle;break; + case actSearchGenreYearTitle: result = new mgSearchGenreYearTitle;break; + case actSearchGenreArtistAlbumTitle: result = new mgSearchGenreArtistAlbumTitle;break; + case actExternal0: result = new mgExternal0;break; + case actExternal1: result = new mgExternal1;break; + case actExternal2: result = new mgExternal2;break; + case actExternal3: result = new mgExternal3;break; + case actExternal4: result = new mgExternal4;break; + case actExternal5: result = new mgExternal5;break; + case actExternal6: result = new mgExternal6;break; + case actExternal7: result = new mgExternal7;break; + case actExternal8: result = new mgExternal8;break; + case actExternal9: result = new mgExternal9;break; + case actExternal10: result = new mgExternal10;break; + case actExternal11: result = new mgExternal11;break; + case actExternal12: result = new mgExternal12;break; + case actExternal13: result = new mgExternal13;break; + case actExternal14: result = new mgExternal14;break; + case actExternal15: result = new mgExternal15;break; + case actExternal16: result = new mgExternal16;break; + case actExternal17: result = new mgExternal17;break; + case actExternal18: result = new mgExternal18;break; + case actExternal19: result = new mgExternal19;break; + } + return result; +} + + +mgSelection * +mgAction::selection() +{ + return osd()->selection(); +} + + +mgSelection * +mgAction::collselection() +{ + return osd()->collselection(); +} + diff --git a/mg_actions.h b/mg_actions.h new file mode 100644 index 0000000..cb0602c --- /dev/null +++ b/mg_actions.h @@ -0,0 +1,168 @@ +/*! + * \file mg_actions.h + * \brief Implements all actions for broswing media libraries within VDR + * + * \version $Revision: 1.13 $ + * \date $Date: 2004-12-25 16:52:35 +0100 (Sat, 25 Dec 2004) $ + * \author Wolfgang Rohdewald + * \author Responsible author: $Author: wr61 $ + * + * $Id: mg_actions.h 276 2004-12-25 15:52:35Z wr61 $ + */ + +#ifndef _MG_ACTIONS_H +#define _MG_ACTIONS_H + +#include <string> + +#include <osd.h> +#include <plugin.h> +#include "i18n.h" + +using namespace std; + +class mgSelection; +class mgMenu; +class mgMainMenu; +class mgOsdItem; + +/*! \brief defines all actions which can appear in command submenus. + * Since these values are saved in muggle.state, new actions should + * always be appended. The order does not matter. Value 0 means undefined. + */ +enum mgActions { + actChooseSearch=1, //!< show a menu with all possible search schemas + actToggleSelection, //!< toggle between search and collection view + actSetDefault, //!< set default collection + actUnused1, + actInstantPlay, //!< instant play + actAddAllToCollection, //!< add all items of OSD list to default collection + actRemoveAllFromCollection,//!< remove from default collection + actDeleteCollection, //!< delete collection + actExportTracklist, //!< export track list into a *.m3u file + actUnused2, + actUnused3, + actAddThisToCollection, //!< add selected item to default collection + actRemoveThisFromCollection, //!< remove selected item from default collection + actEntry, //!< used for normal data base items + actSetButton, //!< connect a button with an action + actSearchCollItem, //!< search in the collections + actSearchArtistAlbumTitle, //!< search by Artist/Album/Title + actSearchArtistTitle, //!< search by Artist/Title + actSearchAlbumTitle, //!< search by Album/Title + actSearchGenreYearTitle, //!< search by Genre1/Year/Title + actSearchGenreArtistAlbumTitle, //!< search by Genre1/Artist/Album/Title + actExternal0, //!< used for external commands, the number is the entry number in the .conf file starting with line 0 + actExternal1, //!< used for external commands, the number is the entry number in the .conf file + actExternal2, //!< used for external commands, the number is the entry number in the .conf file + actExternal3, //!< used for external commands, the number is the entry number in the .conf file + actExternal4, //!< used for external commands, the number is the entry number in the .conf file + actExternal5, //!< used for external commands, the number is the entry number in the .conf file + actExternal6, //!< used for external commands, the number is the entry number in the .conf file + actExternal7, //!< used for external commands, the number is the entry number in the .conf file + actExternal8, //!< used for external commands, the number is the entry number in the .conf file + actExternal9, //!< used for external commands, the number is the entry number in the .conf file + actExternal10, //!< used for external commands, the number is the entry number in the .conf file + actExternal11, //!< used for external commands, the number is the entry number in the .conf file + actExternal12, //!< used for external commands, the number is the entry number in the .conf file + actExternal13, //!< used for external commands, the number is the entry number in the .conf file + actExternal14, //!< used for external commands, the number is the entry number in the .conf file + actExternal15, //!< used for external commands, the number is the entry number in the .conf file + actExternal16, //!< used for external commands, the number is the entry number in the .conf file + actExternal17, //!< used for external commands, the number is the entry number in the .conf file + actExternal18, //!< used for external commands, the number is the entry number in the .conf file + actExternal19, //!< used for external commands, the number is the entry number in the .conf file +}; + +//! \brief the highest possible actExternal value +const mgActions actExternalHigh = actExternal19; + +//! \brief a generic class for the definition of user actions +class mgAction +{ + public: + + //! \brief if true, can be displayed + virtual bool Enabled(); + + //! \brief the action to be executed + virtual void Execute () = 0; + + //! \brief handles the kBack key + virtual eOSState Back(); + +/*! \brief the name for a button. + * The returned C string should be static, it will never be freed. + */ + virtual const char *ButtonName () + { + return NULL; + } + +/*! \brief the name for a menu entry. If empty, no button will be able + * to execute this. The returned C string must be freeable at any time. + * \todo use virtual Set() instead? Compare definition of mgMenu::AddAction + */ + virtual const char *MenuName (const unsigned int idx=0,const string value="") = 0; + + //! \brief default constructor + mgAction (); + + //! \brief default destructor + virtual ~ mgAction (); + + //! \brief assoiates the action with the menu which created it + void SetMenu (mgMenu * const menu); + + //! \brief what to do when mgStatus::OsdCurrentItem is called + //for this one + virtual void Notify(); + protected: + //! \brief returns the OSD owning the menu owning this item + mgMainMenu* osd (); + + //! \brief the menu that created this object + mgMenu * m; + + //! \brief returns the active selection + mgSelection* selection (); + + //! \brief returns the collection selection + mgSelection* collselection (); + + //! \brief returns the playselection + mgSelection* playselection (); +}; + +//! \brief a generic class for all actions that can be used in the +// command submenu +class mgOsdItem : public mgAction, public cOsdItem +{ + public: + //! \brief the enum mgActions value of this class + virtual mgActions Type(); + + //! \brief to be executed for kBack. We might want to call this + // directly, so expose it. + virtual eOSState Back(); +}; + +//! \brief generate a mgOsdItem for action +mgOsdItem* actGenerate(const mgActions action); + + +/*! \brief create collection directly in the collection list + */ +class mgCreateCollection:public cMenuEditStrItem, public mgAction +{ + public: + mgCreateCollection(); + void Notify(); + bool Enabled(); + eOSState ProcessKey(eKeys key); + void Execute (); + const char *MenuName (const unsigned int idx=0,const string value=""); + private: + char value[30]; +}; +#endif diff --git a/mg_content_interface.c b/mg_content_interface.c deleted file mode 100755 index 79dbd1f..0000000 --- a/mg_content_interface.c +++ /dev/null @@ -1,241 +0,0 @@ -/*! \file mg_content_interface.c - * \brief Data Objects for content (e.g. mp3 files, movies) for the vdr muggle plugin - * - * \version $Revision: 1.6 $ - * \date $Date$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author$ - * - * Implements main classes of for content items and abstract interfaces to media access - * - * This file implements the following classes - * - mgTracklist - * - mgSelectionTreeNode - */ -#define DEBUG - -#include <algorithm> -#include "mg_content_interface.h" -#include "mg_tools.h" - - -//! \brief a special item representing an undefined state -mgContentItem mgContentItem::UNDEFINED = mgContentItem(); - -mgTracklist::mgTracklist() -{ - -} - -mgTracklist::~mgTracklist() -{ - mgContentItem* ptr; - std::vector<mgContentItem*>::iterator iter; - - for( iter = m_list.begin(); iter != m_list.end(); iter++ ) - { - ptr = *iter; - delete ptr; - } - m_list.clear(); -} - -std::vector<mgContentItem*> *mgTracklist::getAll() -{ - return &m_list; -} - -unsigned int mgTracklist::getNumItems() -{ - return m_list.size(); -} - -unsigned long mgTracklist::getLength() -{ - unsigned long result = 0; - std::vector<mgContentItem*>::iterator iter; - - for( iter = m_list.begin(); iter != m_list.end (); iter++ ) - { - result += (*iter)->getLength(); - } - - return result; -} - -void mgTracklist::shuffle() -{ - random_shuffle( m_list.begin(), m_list.end () ); -} - -void mgTracklist::sortBy(int col, bool direction) -{ -} - -void mgTracklist::setDisplayColumns(std::vector<int> cols) -{ - m_columns = cols; -} - -unsigned int mgTracklist::getNumColumns() -{ - return m_columns.size(); -} - -std::string mgTracklist::getLabel(unsigned int position, const std::string separator) -{ - std::string label = ""; - mgContentItem* item; - - if( position >= m_list.size() ) - { - return ""; - } - else - { - item = *( m_list.begin() + position ); - } - - for( std::vector<int>::iterator iter = m_columns.begin(); - iter != m_columns.end(); iter++ ) - { - if( iter != m_columns.begin() ) - { - label += separator; - } - label += item->getLabel(*iter); - } - return label; -} - -mgContentItem* mgTracklist::getItem(unsigned int position) -{ - if( position >= m_list.size() ) - { - return &(mgContentItem::UNDEFINED); //invalid - } - return *( m_list.begin() + position); -} - -bool mgTracklist::remove(unsigned position) -{ - bool result = false; - - if( position < (int)m_list.size() ) - { - std::vector<mgContentItem*>::iterator iter; - - iter = m_list.begin() + position; - m_list.erase(iter); - - result = true; - } - - return result; -} - -int mgTracklist::removeItem(mgContentItem* item) -{ - int retval = 0; - std::vector<mgContentItem*>::iterator iter; - - for( iter = m_list.begin(); iter != m_list.end (); iter++ ) - { - if( *iter == item ) - { - m_list.erase(iter); - retval++; - break; - } - } - return retval; -} - -mgSelectionTreeNode::mgSelectionTreeNode(MYSQL db, int view) -{ - m_db = db; - m_parent = NULL; - m_level = 0; - m_view = view; - m_id = ""; - m_label = "ROOT"; - m_expanded = false; -} -mgSelectionTreeNode::mgSelectionTreeNode(mgSelectionTreeNode* parent, std::string id, std::string label) -{ - m_parent = parent; - m_level = m_parent->m_level+1; - m_view = m_parent->m_view; - m_db = m_parent->m_db; - m_id = id; - m_label = label; - m_expanded = false; -} - -mgSelectionTreeNode::~mgSelectionTreeNode() -{ - collapse(); - // TODO - lvw - // _children.clear(); -} - -mgSelectionTreeNode* mgSelectionTreeNode::getParent() -{ - if (m_view < 100 || m_level > 1) - { - return m_parent; - } - else - { - return NULL; - } -} - -void mgSelectionTreeNode::collapse() // removes all children (recursively) -{ - std::vector <mgSelectionTreeNode*>::iterator iter; - mgSelectionTreeNode* ptr; - - for(iter = m_children.begin(); iter != m_children.end();iter++) - { - ptr = *iter; - delete ptr; - } - m_expanded = false; - m_children.clear(); -} - -std::vector<mgSelectionTreeNode*> &mgSelectionTreeNode::getChildren() -{ - // mgDebug(5," returning %d children", m_children.size()); - return m_children; -} - -std::string mgSelectionTreeNode::getID() -{ - return m_id; -} - -std::string mgSelectionTreeNode::getLabel() -{ - return m_label; -} - -std::string mgSelectionTreeNode::getLabel(int n) -{ - mgSelectionTreeNode* node = this; - int d = m_level; - while(n < d) - { - // TODO: check for NULL - node = node->m_parent; - d--; - } - - return node->m_label; -} - -std::string mgSelectionTreeNode::getRestrictions() -{ - return m_restriction; -} diff --git a/mg_content_interface.h b/mg_content_interface.h deleted file mode 100755 index 38edffa..0000000 --- a/mg_content_interface.h +++ /dev/null @@ -1,480 +0,0 @@ -/*! \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$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author file owner: $Author$ - * - * Declares generic classes of for content items and interfaces to SQL databases - * - * This file defines the following classes - * - mgMediaPlayer - * - mgContentItem - * - mgTracklist - * - mgSelectionTreeNode - */ - -/* makes sure we dont parse the same declarations twice */ -#ifndef _CONTENT_INTERFACE_H -#define _CONTENT_INTERFACE_H - -#include <string> -#include <vector> - -#include <mysql/mysql.h> - -#define ILLEGAL_ID -1 - -class mgFilter; - -/*! - * \brief dummy player class - * \ingroup muggle - * - * \todo what to do with this - */ -class mgMediaPlayer -{ - public: - - mgMediaPlayer() - { } - - ~mgMediaPlayer() - { } -}; - -/*! - * \brief Generic class that represents a single content item. - * - * This is the parent class from which classes like mgGdTrack are derived. - * - */ -class mgContentItem -{ - public: - - /*! - * \brief defines the content type of the item - * \todo rethink this mechanism because adding new subclasses - * breaks existing ones (makes recompile cycle necessary). - */ - typedef enum contentType - { - ABSTRACT = 0, //< an abstract item which cannot be used - GD_AUDIO, //< a GiantDisc audio track - EPG //< an EPG item (i.e. a TV show) - }; - - protected: - - /*! - * \brief internal identifier to uniquely identify a content item; - */ - int m_uniqID; - - public: - - /*! - * \brief default constructor - */ - mgContentItem() - : m_uniqID( -1 ) - { } - - /*! - * \brief constructor with explicit id - */ - mgContentItem( int id ) - : m_uniqID( id ) - { } - - /*! - * \brief copy constructor - */ - mgContentItem( const mgContentItem& org ) - : m_uniqID( org.m_uniqID ) - { } - - /*! - * \brief the destructor - */ - virtual ~mgContentItem() - { }; - - /*! - * \brief acess unique id - */ - int getId() - { - return m_uniqID; - } - - /*! - * \brief determine what type of content are we looking at (e.g. audio, video, epg) - * - * The method should be overriden for concrete subclasses to return concrete a contentType. - */ - virtual contentType getContentType() - { - return ABSTRACT; - } - - /*! - * \brief return a (global?) object that is used to play content items - * \todo What for? Interesting properties? Last state, play info, ...? - */ - virtual mgMediaPlayer getPlayer() - { - return mgMediaPlayer(); - } - - //! \brief Access item data - //@{ - - /*! - * \brief return a specific label - */ - virtual std::string getLabel(int col = 0) - { - return ""; - } - - /*! - * \brief return the title - */ - virtual std::string getTitle() - { - return ""; - } - - /*! \brief get the "file" (or URL) that is passed to the player - */ - virtual std::string getSourceFile() - { - return ""; - } - - /*! \brief return a short textual description - */ - virtual std::string getDescription() - { - return ""; - } - - /*! \brief obtain the genre to which the track belongs - */ - virtual std::string getGenre() - { - return ""; - } - - /*! \brief obtain the rating of the title - */ - virtual int getRating() - { - return 0; - } - - /*! \brief obtain the samplerate of the track - */ - virtual int getSampleRate() - { - return 0; - } - - /*! \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; - } - - // check, if usable - virtual bool isValid() - { - return ( m_uniqID >= 0 ); - } - static mgContentItem UNDEFINED; -}; - - -/*! - * \brief a list of content items - * \ingroup muggle - * - * \todo check, whether this class really needs a current item etc. - */ -class mgTracklist -{ - protected: - std::vector<mgContentItem*> m_list; - std::vector<int> m_columns; - int sorting; - - public: - - /*! - * \brief constructor - * - * create an empty tracklist - */ - mgTracklist(); - - /*! - * \brief the destructor - * - * Deletes all items in the tracklist and removes the list itself - */ - virtual ~mgTracklist(); - - /*! - * \brief returns a pointer to the list of elements - */ - std::vector<mgContentItem*> *getAll(); - - /*! - * \brief returns the number of elements in the list - */ - unsigned int getNumItems(); - - /*! - * \brief returns the complete length of the playlist in seconds - */ - unsigned long getLength(); - - /*! - * \brief randomizes the order of the elements in the list - */ - virtual void shuffle(); - - /*! - * \brief sorts the elements in the list by the nth column - */ - virtual void sortBy(int col, bool direction); - - /*! - * \brief stores the ids of columns to be used in label creation - * - * The list can create a label with different fields (columns) using the - * function getLabel(). This function defines the fields of the contentItems - * to be used in the label and their order. - */ - void setDisplayColumns(std::vector<int> cols); - - /*! - * \brief returns the number of display columns - */ - unsigned int getNumColumns(); - - /*! - * \brief creates the label string for an item - * - * The list can create a label with different fields (columns). - * The fields used in the list and their order is set using the function setDisplayColumns. - * - * This function creates a string from these columns, separated by the string - * 'separator' in the label and their order. - */ - virtual std::string getLabel(unsigned int position, const std::string separator); - - /*! - * \brief returns an item from the list at the specified position - */ - virtual mgContentItem* getItem(unsigned int position); - - /*! - * \brief remove item at position - */ - virtual int removeItem(mgContentItem* item); // remove all occurences of item - - /*! - * \brief remove all occurences of item - */ - virtual bool remove(unsigned position); // remove item at position -}; - -/*! - * \brief represent a node in a tree of selections - * \ingroup muggle - * - * The class represents a tree representation. Each node can have a parent node and - * an arbitrary number of children nodes. - */ -class mgSelectionTreeNode -{ - protected: - - /*! - * \brief the database in which a node is stored - * \todo should this be in the authority of concrete subclasses? - */ - MYSQL m_db; - - //! \brief maintain a flag, whether the node is currently expanded - bool m_expanded; - - //! \brief list of active restrictions at this level - std::string m_restriction; - - //! \brief depth of node in the tree (0 = root) - int m_level; - - //! \brief unknown - int m_view; - - //! \brief ID of the node, used for further expand - std::string m_id; - - //! \brief label of the node, used for user interaction - std::string m_label; - - //! \brief parent of this node - mgSelectionTreeNode* m_parent; - - //! \brief hold the set of immediate children if expanded, empty if collapsed - std::vector <mgSelectionTreeNode*> m_children; - - public: - - //! \brief Object lifecycle management - //@{ - - /*! - * \brief a constructor for an empty node - */ - mgSelectionTreeNode(MYSQL db, int view); - - /*! - * \brief a constructor for a node with a parent - */ - mgSelectionTreeNode(mgSelectionTreeNode* parent, std::string id, std::string label); - - /*! - * \brief the destructor - */ - virtual ~mgSelectionTreeNode(); - - //@} - - //! \brief expand and collapse tree - //@{ - - /*! - * \brief whether the node is a leaf (i.e. has no more children) - */ - virtual bool isLeafNode() = 0; - - /*! - * \brief expand the node - * - * The method will obtain all its children node, e.g. from a database - */ - virtual bool expand() = 0; - - /*! - * \brief collapse all children nodes - * - * The method will collapse the subtree below this node and - * destroy all children node objects. - */ - virtual void collapse(); // removes all children (recursively) - - /*! - * \brief obtain parent node - * - * \todo what is that magic number 100 for in the implementation? - */ - mgSelectionTreeNode* getParent(); - - /*! - * \brief access direct children of the node - */ - virtual std::vector<mgSelectionTreeNode*> &getChildren(); - - /*! - * \brief returns all tracks which are children of this node (transitive closure!) - * - * This function allocates memory for the vector and for all elements of the vector - * The calling function is in charge of releasing this memory - */ - virtual std::vector<mgContentItem*>* getTracks() = 0; - - /*! - * \brief obtain a single track - */ - virtual mgContentItem* getSingleTrack() = 0; - - bool isExpanded() - { return m_expanded; } - - int getLevel() - { return m_level; } - - //@} - - //! \brief obtain node information - //@{ - - /*! - * \brief obtain the ID of this node - */ - std::string getID(); - - /*! - * \brief obtain the label of this node - */ - virtual std::string getLabel(int n); - - /*! - * \brief obtain the label from the topmost parent of this node - */ - std::string getLabel(); - - /*! - * \brief obtain a SQL restriction - * - * The restriction returned is part of a SQL query string which will restrict - * results to nodes that belong to the set of items grouped by this node - */ - virtual std::string getRestrictions(); - - //@} - -}; - -#endif diff --git a/mg_database.c b/mg_database.c index 02d2e0a..1636262 100644 --- a/mg_database.c +++ b/mg_database.c @@ -10,7 +10,6 @@ #include "mg_database.h" #include "mg_tools.h" -#include <stdio.h> #include <stdarg.h> static const int MAX_QUERY_BUFLEN = 2048; @@ -0,0 +1,1517 @@ +/*! + * \file mg_db.c + * \brief A database interface to the GiantDisc + * + * \version $Revision: 1.0 $ + * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ + * \author Wolfgang Rohdewald + * \author Responsible author: $Author: wr $ + * + */ + +#include "mg_db.h" +#include "vdr_setup.h" +#include "mg_tools.h" + +/*! \brief a RAM copy of the genres table for faster access + * and in order to avoid having to use genre as genre 1 etc. + */ +static map < string, string > genres; + +//! \brief adds string n to string s, using string sep to separate them +static string addsep (string & s, string sep, string n); + +//! \brief adds string n to string s, using a comma to separate them +static string comma (string & s, string n); + +//! \brief adds string n to string s, using AND to separate them +static string und (string & s, string n); + +/*! \brief returns a random integer within some range + */ +unsigned int +randrange (const unsigned int high) +{ + unsigned int result=0; + result = random () % high; + return result; +} + + +//! \brief adds n1=n2 to string s, using AND to separate several such items +static string +undequal (string & s, string n1, string op, string n2) +{ + if (n1.compare (n2) || op != "=") + return addsep (s, " AND ", n1 + op + n2); + else + return s; +} + +static string +comma (string & s, string n) +{ + return addsep (s, ",", n); +} + + +static string +und (string & s, string n) +{ + return addsep (s, " AND ", n); +} + + +static string +commalist (string prefix,list < string > v,bool sort=true) +{ + string result = ""; + if (sort) v.sort (); + v.unique (); + for (list < string >::iterator it = v.begin (); it != v.end (); it++) + { + comma (result, *it); + } + if (!result.empty()) + result.insert(0," "+prefix+" "); + return result; +} + +//! \brief converts long to string +string +itos (int i) +{ + stringstream s; + s << i; + return s.str (); +} + +//! \brief convert long to string +string +ltos (long l) +{ + stringstream s; + s << l; + return s.str (); +} + +static string zerostring; + +size_t +mgSelection::mgSelStrings::size() +{ + if (!m_sel) + mgError("mgSelStrings: m_sel is NULL"); + m_sel->refreshValues(); + return strings.size(); +} + +string& +mgSelection::mgSelStrings::operator[](unsigned int idx) +{ + if (!m_sel) + mgError("mgSelStrings: m_sel is NULL"); + m_sel->refreshValues(); + if (idx>=strings.size()) return zerostring; + return strings[idx]; +} + +void +mgSelection::mgSelStrings::setOwner(mgSelection* sel) +{ + m_sel = sel; +} + +mgValmap::mgValmap(const char *key) { + m_key = key; +} + +void mgValmap::Read(FILE *f) { + char *line=(char*)malloc(1000); + char *prefix=(char*)malloc(strlen(m_key)+2); + strcpy(prefix,m_key); + strcat(prefix,"."); + rewind(f); + while (fgets(line,1000,f)) { + if (strncmp(line,prefix,strlen(prefix))) continue; + if (line[strlen(line)-1]=='\n') + line[strlen(line)-1]=0; + char *name = line + strlen(prefix); + char *eq = strchr(name,'='); + if (!eq) continue; + *(eq-1)=0; + char *value = eq + 2; + (*this)[string(name)]=string(value); + } + free(prefix); + free(line); +} + +void mgValmap::Write(FILE *f) { + for (mgValmap::const_iterator it=begin();it!=end();++it) { + char b[1000]; + sprintf(b,"%s.%s = %s\n", + m_key,it->first.c_str(), + it->second.c_str()); + fputs(b,f); + } +} + +void mgValmap::put(const char* name, const string value) { + if (value.empty() || value==EMPTY) return; + (*this)[string(name)] = value; +} + +void mgValmap::put(const char* name, const char* value) { + if (!value || *value==0) return; + (*this)[string(name)] = value; +} + +void mgValmap::put(const char* name, const int value) { + put(name,ltos(value)); +} + +void mgValmap::put(const char* name, const unsigned int value) { + put(name,ltos(value)); +} + +void mgValmap::put(const char* name, const long value) { + put(name,ltos(value)); +} + +void mgValmap::put(const char* name, const bool value) { + string s; + if (value) + s = "true"; + else + s = "false"; + put(name,s); +} + + +void +mgSelection::clearCache() +{ + m_current_values = ""; + m_current_tracks = ""; +} + +string +mgSelection::getCurrentValue() +{ + return values[gotoPosition()]; +} + +MYSQL_RES * +mgSelection::exec_sql (string query) +{ + mgDebug(3,query.c_str()); + if (!m_db) return NULL; + if (mysql_query (m_db, (query + ';').c_str ())) + { + mgError("SQL Error in %s: %s",query.c_str(),mysql_error (m_db)); + return NULL; + } + return mysql_store_result (m_db); +} + + +/*! \brief executes a query and returns the first columnu of the + * first row. + * \param query the SQL query string to be executed + */ +string mgSelection::get_col0 (string query) +{ + MYSQL_RES * sql_result = exec_sql (query); + if (!sql_result) + return "NULL"; + MYSQL_ROW row = mysql_fetch_row (sql_result); + string result; + if (row == NULL) + result = "NULL"; + else if (row[0] == NULL) + result = "NULL"; + else + result = row[0]; + mysql_free_result (sql_result); + return result; +} + + +unsigned long +mgSelection::exec_count (string query) +{ + return atol (get_col0 (query).c_str ()); +} + + +/*! \brief extract table names. All words preceding a . are supposed to be + * table names. Table names are supposed to only contain letters. That is + * sufficient for GiantDisc + * \par spar the SQL command + * \return a list of table names + * \todo is this thread safe? + */ +static list < string > +tables (const string spar) +{ + list < string > result; + string s = spar; + string::size_type dot; + while ((dot = s.rfind ('.')) != string::npos) + { + s.erase (dot, string::npos); // cut the rest + string::size_type lword = s.size (); + while (strchr + ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + s[lword - 1])) + { + lword--; + if (lword == 0) + break; + } + result.push_back (s.substr (lword)); + } + return result; +} + + +/*! \brief if the SQL command works on only 1 table, remove all table + * qualifiers. Example: SELECT tracks.title FROM tracks becomes SELECT title + * FROM tracks + * \param spar the sql command. It will be edited in place + * \return the new sql command is also returned + */ +static string +optimize (string & spar) +{ + string s = spar; + string::size_type tmp = s.find (" WHERE"); + if (tmp != string::npos) + s.erase (tmp, 9999); + tmp = s.find (" ORDER"); + if (tmp != string::npos) + s.erase (tmp, 9999); + string::size_type frompos = s.find (" FROM ") + 6; + if (s.substr (frompos).find (",") == string::npos) + { + string from = s.substr (frompos, 999) + '.'; + string::size_type track; + while ((track = spar.find (from)) != string::npos) + { + spar.erase (track, from.size ()); + } + } + return spar; +} + +string keyfield::sql_string(const string s) const +{ + return selection->sql_string(s); +} + +keyfield::keyfield (const string choice) +{ + if (choice.empty()) + mgError("keyfield::keyfield: choice is empty"); + m_choice = choice; + m_id = EMPTY; + m_value = ""; + m_filter = ""; +} + + +string +mgSelection::sql_string (const string s) const +{ + char *buf = (char *) malloc (s.size () * 2 + 1); + mysql_real_escape_string (m_db, buf, s.c_str (), s.size ()); + string result = "'" + std::string (buf) + "'"; + free (buf); + return result; +} + + +string keyfield::restrict (string & result) const +{ + string id = ""; + string op; + if (m_id == EMPTY) + return result; + if (m_id == "NULL") + { + op = " is "; + id = "NULL"; + } + else + { + op = "="; + id = sql_string (m_id); + } + if (idfield () == valuefield ()) + undequal (result, idfield (), op, id); + else + undequal (result, basefield (), op, id); + return result; +} + + +string keyfield::join () const +{ + string result; + if (need_join ()) + return undequal (result, basefield (), "=", idfield ()); + else + return ""; +} + + +bool keyfield::need_join () const +{ + return lookup; +} + +void +keyfield::set(const string id,const string value) +{ + if (id != EMPTY) + if (m_id == id && m_value == value) return; + m_id = id; + m_value = value; + if (selection) + selection->clearCache(); +} + +void +keyfield::writeAt (ostream & s) const +{ + if (m_id == EMPTY) + s << choice () << '/'; + else + s << choice () << '=' << m_id; +} + + +const char * +toCString (mgSelection::ShuffleMode m) +{ + static const char *modes[] = + { + "SM_NONE", "SM_NORMAL", "SM_PARTY" + }; + return modes[m]; +} + + +string +toString (mgSelection::ShuffleMode m) +{ + return toCString (m); +} + +//! \brief dump keyfield +ostream & operator<< (ostream & s, keyfield & k) +{ + k.writeAt (s); + return s; +} + + +string +addsep (string & s, string sep, string n) +{ + if (!n.empty ()) + { + if (!s.empty ()) + s.append (sep); + s.append (n); + } + return s; +} + + +mgContentItem * +mgSelection::getTrack (unsigned int position) +{ + if (position >= getNumTracks ()) + return NULL; + return &(m_tracks[position]); +} + + +void +mgSelection::loadgenres () +{ + MYSQL_RES *rows = exec_sql ("select id,genre from genre;"); + if (rows) + { + MYSQL_ROW row; + while ((row = mysql_fetch_row (rows)) != NULL) + { + genres[row[0]] = row[1]; + } + mysql_free_result (rows); + } +} + + +string mgContentItem::getGenre1 () +{ + return genres[m_genre1]; +} + + +string mgContentItem::getGenre2 () +{ + return genres[m_genre2]; +} + + +mgSelection::ShuffleMode mgSelection::toggleShuffleMode () +{ + m_shuffle_mode = (m_shuffle_mode == SM_PARTY) ? SM_NONE : ShuffleMode (m_shuffle_mode + 1); + unsigned int tracksize = getNumTracks(); + switch (m_shuffle_mode) + { + case SM_NONE: + { + long id = m_tracks[m_tracks_position].getId (); + m_current_tracks = ""; // force a reload + for (unsigned int i = 0; i < tracksize; i++) + if (m_tracks[i].getId () == id) + { + m_tracks_position = i; + break; + } + } + break; + case SM_PARTY: + case SM_NORMAL: + { + // play all, beginning with current track: + mgContentItem tmp = m_tracks[m_tracks_position]; + m_tracks[m_tracks_position]=m_tracks[0]; + m_tracks[0]=tmp; + m_tracks_position=0; + // randomize all other tracks + for (unsigned int i = 1; i < tracksize; i++) + { + unsigned int j = 1+randrange (tracksize-1); + tmp = m_tracks[i]; + m_tracks[i] = m_tracks[j]; + m_tracks[j] = tmp; + } + } break; +/* + * das kapiere ich nicht... (wolfgang) + - Party mode (see iTunes) + - initialization + - find 15 titles according to the scheme below + - playing + - before entering next title perform track selection + - track selection + - generate a random uid + - if file exists: + - determine maximum playcount of all tracks +- generate a random number n +- if n < playcount / max. playcount +- add the file to the end of the list +*/ + } + return m_shuffle_mode; +} + + +mgSelection::LoopMode mgSelection::toggleLoopMode () +{ + m_loop_mode = (m_loop_mode == LM_FULL) ? LM_NONE : LoopMode (m_loop_mode + 1); + return m_loop_mode; +} + + +unsigned int +mgSelection::AddToCollection (const string Name) +{ + if (!m_db) return 0; + CreateCollection(Name); + string listid = sql_string (get_col0 + ("SELECT id FROM playlist WHERE title=" + sql_string (Name))); + string tmp = + get_col0 ("SELECT MAX(tracknumber) FROM playlistitem WHERE playlist=" + + listid); + int high; + if (tmp == "NULL") + high = 0; + else + high = atol (tmp.c_str ()); + unsigned int tracksize = getNumTracks (); + const char *sql_prefix = "INSERT INTO playlistitem VALUES "; + string sql = ""; + for (unsigned int i = 0; i < tracksize; i++) + { + string value = "(" + listid + "," + ltos (high + 1 + i) + "," + + ltos (m_tracks[i].getId ()) + ")"; + comma(sql, value); + if ((i%100)==99) + { + exec_sql (sql_prefix+sql); + sql = ""; + } + } + if (!sql.empty()) exec_sql (sql_prefix+sql); + if (inCollection(Name)) clearCache (); + return tracksize; +} + + +unsigned int +mgSelection::RemoveFromCollection (const string Name) +{ + if (!m_db) return 0; + string listid = get_col0 + ("SELECT id FROM playlist WHERE title=" + sql_string (Name)); + where(); + m_fromtables.push_back("playlistitem"); + m_fromtables.push_back("playlist"); + string sql = "DELETE playlistitem" + commalist("FROM",m_fromtables) + + m_where + " AND tracks.id = playlistitem.trackid " + " AND playlistitem.playlist = " + listid; + exec_sql (sql); + unsigned int removed = mysql_affected_rows (m_db); + if (inCollection(Name)) clearCache (); + return removed; +} + + +bool mgSelection::DeleteCollection (const string Name) +{ + if (!m_db) return false; + exec_sql ("DELETE FROM playlist WHERE title=" + sql_string (Name)); + if (isCollectionlist()) clearCache (); + return (mysql_affected_rows (m_db) == 1); +} + + +void mgSelection::ClearCollection (const string Name) +{ + if (!m_db) return; + exec_sql ("DELETE playlistitem FROM playlist,playlistitem " + "WHERE playlistitem.playlist=playlist.id " + " AND playlist.title=" + sql_string (Name)); + if (inCollection(Name)) clearCache (); +} + + +bool mgSelection::CreateCollection(const string Name) +{ + if (!m_db) return false; + string name = sql_string(Name); + if (exec_count("SELECT count(title) FROM playlist WHERE title = " + name)>0) + return false; + exec_sql ("INSERT playlist VALUES(" + name + ",'VDR',NULL,NULL,NULL)"); + if (isCollectionlist()) clearCache (); + return true; +} + + +string mgSelection::exportM3U () +{ + +// open a file for writing + string fn = m_Directory + '/' + ListFilename () + ".m3u"; + FILE * listfile = fopen (fn.c_str (), "w"); + if (!listfile) + return ""; + fprintf (listfile, "#EXTM3U"); + unsigned int tracksize = getNumTracks (); + for (unsigned i = 0; i < tracksize; i++) + { + mgContentItem* t = &m_tracks[i]; + fprintf (listfile, "#EXTINF:%d,%s\n", t->getDuration (), + t->getTitle ().c_str ()); + fprintf (listfile, "%s", t->getSourceFile ().c_str ()); + } + fclose (listfile); + return fn; +} + +bool +mgSelection::empty() +{ + if (m_level>= keys.size ()-1) + return ( getNumTracks () == 0); + else + return ( values.size () == 0); +} + +void +mgSelection::setPosition (unsigned int position) +{ + if (m_level < keys.size ()) + m_position[m_level] = position; + if (m_level >= keys.size ()-1) + m_tracks_position = position; +} + + +void +mgSelection::setTrack (unsigned int position) +{ + m_tracks_position = position; +} + + +unsigned int +mgSelection::getPosition (unsigned int level) const +{ + if (level == keys.size ()) + return getTrackPosition(); + else + return m_position[m_level]; +} + +unsigned int +mgSelection::gotoPosition (unsigned int level) +{ + if (level>keys.size()) + mgError("mgSelection::gotoPosition: level %u > keys.size %u", + level,keys.size()); + if (level == keys.size ()) + return gotoTrackPosition(); + else + { + unsigned int valsize = values.size(); + if (valsize==0) + m_position[m_level] = 0; + else if (m_position[m_level] >= valsize) + m_position[m_level] = valsize -1; + return m_position[m_level]; + } +} + +unsigned int +mgSelection::getTrackPosition() const +{ + return m_tracks_position; +} + +unsigned int +mgSelection::gotoTrackPosition() +{ + unsigned int tracksize = getNumTracks (); + if (tracksize == 0) + m_tracks_position = 0; + else if (m_tracks_position >= tracksize) + m_tracks_position = tracksize -1; + return m_tracks_position; +} + +bool mgSelection::skipTracks (int steps) +{ + unsigned int tracksize = getNumTracks(); + if (tracksize == 0) + return false; + if (m_loop_mode == LM_SINGLE) + return true; + unsigned int old_pos = getTrackPosition(); + unsigned int new_pos; + if (old_pos + steps < 0) + { + if (m_loop_mode == LM_NONE) + return false; + new_pos = tracksize - 1; + } + else + new_pos = old_pos + steps; + if (new_pos >= tracksize) + { + clearCache(); + tracksize = getNumTracks(); + if (new_pos >= tracksize) + { + if (m_loop_mode == LM_NONE) + return false; + new_pos = 0; + } + } + setTrack (new_pos); + return (new_pos == gotoTrackPosition()); +} + + +unsigned long +mgSelection::getLength () +{ + unsigned long result = 0; + unsigned int tracksize = getNumTracks (); + for (unsigned int i = 0; i < tracksize; i++) + result += m_tracks[i].getDuration (); + return result; +} + + +unsigned long +mgSelection::getCompletedLength () +{ + unsigned long result = 0; + tracks (); // make sure they are loaded + for (unsigned int i = 0; i < m_tracks_position; i++) + result += m_tracks[i].getDuration (); + return result; +} + + +string mgSelection::getListname () +{ + string + result = ""; + for (unsigned int i = 0; i < m_level; i++) + addsep (result, ":", keys[i]->value ()); + if (result.empty ()) + result = string(tr(keys[0]->choice ().c_str())); + return result; +} + + +string mgSelection::ListFilename () +{ + string res = getListname (); +#if 0 + geht so noch gar + nicht ... while (string::size_type p = res.find (" ")) + res.replace (p, ""); + while (string::size_type p = res.find ("/")) + res.replace (p, '-'); + while (string::size_type p = res.find ("\\")) + res.replace (p, '-'); +#endif + return res; +} + +void +mgSelection::AddOrder(const string sql,list<string>& orderlist, const string item) +{ + string::size_type dot = item.rfind ('.'); + string itemtable = item.substr(0,dot); + if (sql.find(itemtable) != string::npos) + orderlist.push_back(item); +} + +const vector < mgContentItem > & +mgSelection::tracks () +{ + list < string > orderby; + orderby.clear(); + if (keys.empty()) + mgError("mgSelection::tracks(): keys is empty"); + if (genres.size () == 0) + loadgenres (); + string sql = "SELECT tracks.id, tracks.title, tracks.mp3file, " + "tracks.artist, album.title, tracks.genre1, tracks.genre2, " + "tracks.bitrate, tracks.year, tracks.rating, " + "tracks.length, tracks.samplerate, tracks.channels "; + sql += where (true); + for (unsigned int i = m_level; i<keys.size(); i++) + { + AddOrder(sql,orderby,keys[i]->order ()); +} + if (m_level>= keys.size ()-1) + if (inCollection()) + AddOrder(sql,orderby,"playlistitem.tracknumber"); + else + AddOrder(sql,orderby,"tracks.title"); + + + sql += commalist("ORDER BY",orderby,false); + + optimize (sql); + if (m_current_tracks != sql) + { + m_current_tracks = sql; + m_tracks.clear (); + MYSQL_RES *rows = exec_sql (sql); + if (rows) + { + MYSQL_ROW row; + while ((row = mysql_fetch_row (rows)) != NULL) + { + m_tracks.push_back (mgContentItem (row, m_ToplevelDir)); + } + mysql_free_result (rows); + } + if (m_tracks_position>=m_tracks.size()) + if (m_tracks.size()==0) + m_tracks_position=0; + else + m_tracks_position = m_tracks.size()-1; + } + return m_tracks; +} + + +mgContentItem::mgContentItem (const mgContentItem* c) +{ + m_id = c->m_id; + m_title = c->m_title; + m_mp3file = c->m_mp3file; + m_artist = c->m_artist; + m_albumtitle = c->m_albumtitle; + m_genre1 = c->m_genre1; + m_genre2 = c->m_genre2; + m_bitrate = c->m_bitrate; + m_year = c->m_year; + m_rating = c->m_rating; + m_duration = c->m_duration; + m_samplerate = c->m_samplerate; + m_channels = c->m_channels; +} + +mgContentItem::mgContentItem (const MYSQL_ROW row, const string ToplevelDir) +{ + m_id = atol (row[0]); + m_title = row[1]; + m_mp3file = ToplevelDir + row[2]; + m_artist = row[3]; + m_albumtitle = row[4]; + m_genre1 = row[5]; + m_genre2 = row[6]; + m_bitrate = row[7]; + m_year = atol (row[8]); + if (row[9]) + m_rating = atol (row[9]); + m_duration = atol (row[10]); + m_samplerate = atol (row[11]); + m_channels = atol (row[12]); +}; + +string mgContentItem::getAlbum () +{ + return m_albumtitle; +} + + +string mgContentItem::getImageFile () +{ + return "Name of Imagefile"; +} + + +void +mgSelection::initkey (keyfield & f) +{ + f.setOwner(this); + all_keys[f.choice ()] = &f; + trall_keys[string(tr(f.choice ().c_str()))] = &f; +} + +void mgSelection::InitSelection() { + m_Directory="."; + m_ToplevelDir = string("/"); + InitDatabase(); + m_level = 0; + m_position.reserve (20); + m_tracks_position = 0; + m_trackid = -1; + m_shuffle_mode = SM_NONE; + m_loop_mode = LM_NONE; + clearCache(); + initkey (kartist); + initkey (kgenre1); + initkey (kgenre2); + initkey (klanguage); + initkey (krating); + initkey (kyear); + initkey (kdecade); + initkey (ktitle); + initkey (ktrack); + initkey (kalbum); + initkey (kcollection); + initkey (kcollectionitem); + keys.clear(); + keys.push_back (&kartist); + keys.push_back (&kalbum); + keys.push_back (&ktitle); + values.setOwner(this); +} + +mgSelection::mgSelection() +{ + m_db = NULL; + m_Host = ""; + m_User = ""; + m_Password = ""; + InitSelection (); + m_fall_through = false; +} + +mgSelection::mgSelection (const string Host, const string User, const string Password, const bool fall_through) +{ + m_db = NULL; + m_Host = Host; + m_User = User; + m_Password = Password; + InitSelection (); + m_fall_through = fall_through; +} + +mgSelection::mgSelection (const mgSelection &s) +{ + m_db = NULL; + InitFrom(&s); +} + +mgSelection::mgSelection (const mgSelection* s) +{ + m_db = NULL; + InitFrom(s); +} + +mgSelection::mgSelection (mgValmap& nv) +{ + // this is analog to the copy constructor, please keep in sync. + + m_db = NULL; + InitFrom(nv); +} + +void +mgSelection::InitFrom(mgValmap& nv) +{ + m_Host = nv.getstr("Host"); + m_User = nv.getstr("User"); + m_Password = nv.getstr("Password"); + InitSelection(); + m_fall_through = nv.getbool("FallThrough"); + m_Directory = nv.getstr("Directory"); + m_ToplevelDir = nv.getstr("ToplevelDir"); + for (unsigned int i = 0; i < 99 ; i++) + { + char *idx; + asprintf(&idx,"Keys.%u.Choice",i); + string v = nv.getstr(idx); + free(idx); + if (v.empty()) break; + setKey (i,v ); + } + while (m_level < nv.getuint("Level")) + { + char *idx; + asprintf(&idx,"Keys.%u.Position",m_level); + unsigned int newpos = nv.getuint(idx); + free(idx); + if (!enter (newpos)) + if (!select (newpos)) break; + } + m_trackid = nv.getlong("TrackId"); + // TODO do we really need Position AND TrackPosition in muggle.state? + setPosition(nv.getlong("Position")); + if (m_level>=keys.size()-1) + setTrack(nv.getlong("TrackPosition")); + setShuffleMode(ShuffleMode(nv.getuint("ShuffleMode"))); + setLoopMode(LoopMode(nv.getuint("LoopMode"))); +} + + +mgSelection::~mgSelection () +{ + mysql_close (m_db); +} + +void mgSelection::InitFrom(const mgSelection* s) +{ + m_Host = s->m_Host; + m_User = s->m_User; + m_Password = s->m_Password; + InitSelection(); + m_fall_through = s->m_fall_through; + m_Directory = s->m_Directory; + m_ToplevelDir = s->m_ToplevelDir; + keys.clear(); + for (unsigned int i = 0; i < s->keys.size (); i++) + { + keys.push_back(findKey(s->keys[i]->choice())); + keys[i]->set(s->keys[i]->id(),s->keys[i]->value()); + } + m_level = s->m_level; + m_position.reserve (s->m_position.capacity()); + for (unsigned int i = 0; i < s->m_position.capacity(); i++) + m_position[i] = s->m_position[i]; + m_trackid = s->m_trackid; + m_tracks_position = s->m_tracks_position; + setShuffleMode (s->getShuffleMode ()); + setLoopMode (s->getLoopMode ()); +} + +const mgSelection& mgSelection::operator=(const mgSelection &s) +{ + if ((&m_Host)==&(s.m_Host)) { // prevent s = s + return *this; + } + InitFrom(&s); + return *this; +} + + +void +mgSelection::writeAt (ostream & s) +{ + for (unsigned int i = 0; i < keys.size (); i++) + { + if (i == level ()) + s << '*'; + s << *keys[i] << ' '; + if (i == level ()) + { + for (unsigned int j = 0; j < values.size (); j++) + { + s << values[j]; + if (values[j] != m_ids[j]) + s << '(' << m_ids[j] << ")"; + s << ", "; + if (j == 7) + { + s << "(von " << values.size () << ") "; + break; + } + } + } + } + s << endl; +} + + +ostream & operator<< (ostream & s, mgSelection & sl) +{ + sl.writeAt (s); + return s; +} + + +unsigned int +mgSelection::size () +{ + return keys.size (); +} + + +unsigned int +mgSelection::valindex (const string val,const bool second_try) +{ + for (unsigned int i = 0; i < values.size (); i++) + { + if (values[i] == val) + return i; + } + // nochmal mit neuen Werten: + clearCache(); + if (second_try) { + mgWarning("valindex: Gibt es nicht:%s",val.c_str()); + return 0; + } + else + return valindex(val,true); +} + + +string mgSelection::where (bool want_trackinfo) +{ + m_from = ""; + m_where = ""; + m_fromtables.clear(); + if (m_level < keys.size ()) + { + for (unsigned int i = 0; i <= m_level; i++) + { + keyfield * k = keys[i]; + k->lookup = want_trackinfo || (i == m_level); + list < string > l = tables (k->join () + ' ' + k->basefield ()); + m_fromtables.merge (l); + und (m_where, k->join ()); + k->restrict (m_where); + } + } + else + { + m_fromtables.push_back ("tracks"); + m_where = "tracks.id='" + ltos (m_trackid) + "'"; + } + if (want_trackinfo) + { + if (m_level == keys.size () || !UsedBefore (&kalbum, m_level + 1)) + { + kalbum.lookup = false; + list < string > l = + tables (kalbum.join () + ' ' + kalbum.basefield ()); + m_fromtables.merge (l); + und (m_where, kalbum.join ()); + } + } + m_from = commalist ("FROM",m_fromtables); + if (!m_where.empty ()) + m_where.insert (0, " WHERE "); + return m_from + m_where; +} + + +void +mgSelection::refreshValues () +{ + if (m_current_values.empty()) + { + m_current_values = sql_values(); + values.strings.clear (); + m_ids.clear (); + MYSQL_RES *rows = exec_sql (m_current_values); + if (rows) + { + unsigned int num_fields = mysql_num_fields(rows); + MYSQL_ROW row; + while ((row = mysql_fetch_row (rows)) != NULL) + { + values.strings.push_back (row[0]); + if (num_fields==2) + m_ids.push_back (row[1]); + else + m_ids.push_back (row[0]); + } + mysql_free_result (rows); + } + if (m_position[m_level]>=values.size()) + if (values.size()==0) + m_position[m_level]=0; + else + m_position[m_level] = values.size()-1; + } +} + + +string mgSelection::sql_values () +{ + if (keys.empty()) + mgError("mgSelection::sql_values(): keys is empty"); + string result; + if (m_level < keys.size ()) + { + keyfield * last = keys[m_level]; + result = "SELECT "; + if (m_level<keys.size()-1) result += "DISTINCT "; + result += last->valuefield (); + if (last->valuefield() != last->idfield()) + result += ',' + last->idfield (); + result += where (false); + result += " ORDER BY " + last->order (); + } + else + { + result = "SELECT title,id from tracks where id='" + ltos (m_trackid) + "'"; + } + optimize (result); + return result; +} + + +unsigned int +mgSelection::count () +{ + return values.size (); +} + + +void +mgSelection::InitDatabase () +{ + if (m_db) + { + mysql_close (m_db); + m_db = NULL; + } + if (m_Host == "") return; + m_db = mysql_init (0); + if (m_db == NULL) + return; + if (mysql_real_connect (m_db, m_Host.c_str (), m_User.c_str (), m_Password.c_str (), + "GiantDisc", 0, NULL, 0) == NULL) { + mgWarning("Failed to connect to host '%s' as User '%s', Password '%s': Error: %s", + m_Host.c_str(),m_User.c_str(),m_Password.c_str(),mysql_error(m_db)); + mysql_close (m_db); + m_db = NULL; + return; + } + return; +} + + +string keyfield::KeyCountquery () +{ + lookup = false; + string from; + from = commalist ("FROM",tables (countfield () + ' ' + countjoin ())); + string query = "SELECT COUNT(DISTINCT " + countfield () + ") " + from; + if (!countjoin ().empty ()) + query += " WHERE " + countjoin (); + optimize (query); + return query; +} + +keyfield* mgSelection::findKey(const string name) +{ + if (all_keys.find(name) != all_keys.end()) + return all_keys.find(name)->second; + if (trall_keys.find(name) != trall_keys.end()) + return trall_keys.find(name)->second; + return NULL; +} + +void +mgSelection::setKey (const unsigned int level, const string name) +{ + keyfield *newkey = findKey(name); + if (newkey == NULL) + mgError("mgSelection::setKey(%u,%s): keyname wrong", + level,name.c_str()); + if (level == 0 && newkey == &kcollection) + { + keys.clear (); + keys.push_back (&kcollection); + keys.push_back (&kcollectionitem); + return; + } + if (level == keys.size ()) + { + keys.push_back (newkey); + } + else + { + if (level >= keys.size()) + mgError("mgSelection::setKey(%u,%s): level greater than keys.size() %u", + level,name.c_str(),keys.size()); + keys[level] = newkey; +// remove this key from following lines: + for (unsigned int i = level + 1; i < keys.size (); i++) + if (keys[i] == keys[level]) + keys.erase (keys.begin () + i); + } + +// remove redundant lines: + bool album_found = false; + bool track_found = false; + bool title_found = false; + for (unsigned int i = 0; i < keys.size (); i++) + { + album_found |= (keys[i] == &kalbum); + track_found |= (keys[i] == &ktrack); + title_found |= (keys[i] == &ktitle); + if (track_found || (album_found && title_found)) + { + keys.erase (keys.begin () + i + 1, keys.end ()); + break; + } + } + +// clear values for this and following levels (needed for copy constructor) + for (unsigned int i = level; i < keys.size (); i++) + keys[i]->set (EMPTY, ""); + + if (m_level > level) + m_level = level; + if (m_level == level) setPosition(0); +} + + +bool mgSelection::enter (unsigned int position) +{ + if (keys.empty()) + mgError("mgSelection::enter(%u): keys is empty", position); + if (empty()) + return false; + setPosition (position); + position = gotoPosition(); // reload adjusted position + string value = values[position]; + string id = m_ids[position]; + while (1) + { + mgDebug(2,"enter(level=%u,pos=%u, value=%s)",m_level,position,value.c_str()); + if (m_level >= keys.size () - 1) + return false; + keys[m_level++]->set (id, value); + if (m_level >= keys.size()) + mgError("mgSelection::enter(%u): level greater than keys.size() %u", + m_level,keys.size()); + if (m_position.capacity () == m_position.size ()) + m_position.reserve (m_position.capacity () + 10); + m_position[m_level] = 0; + if (!m_fall_through) + break; + if (count () > 1) + break; + if (count () == 1) + { + id = m_ids[0]; + value = values[0]; + } + } + return true; +} + + +bool mgSelection::select (unsigned int position) +{ + mgDebug(2,"select(pos=%u)",position); + if (m_level == keys.size () - 1) + { + if (getNumTracks () <= position) + return false; + m_level++; + m_trackid = m_tracks[position].getId (); + clearCache(); + return true; + } + else + return enter (position); +} + + +bool mgSelection::leave () +{ + if (keys.empty()) + mgError("mgSelection::leave(): keys is empty"); + if (m_level == keys.size ()) + { + m_level--; + m_trackid = -1; + clearCache(); + return true; + } + while (1) + { + if (m_level < 1) + return false; + keys[--m_level]->set (EMPTY, ""); + if (!m_fall_through) + break; + if (count () > 1) + break; + } + return true; +} + + +bool mgSelection::UsedBefore (keyfield const *k, unsigned int level) +{ + if (level >= keys.size ()) + level = keys.size () - 1; + for (unsigned int i = 0; i < level; i++) + if (keys[i] == k) + return true; + return false; +} + + +bool mgSelection::isCollectionlist () +{ + return (keys[0] == &kcollection && m_level == 0); +} + +bool +mgSelection::inCollection(const string Name) +{ + bool result = (keys[0] == &kcollection && m_level == 1); + if (result) + if (keys[1] != &kcollectionitem) + mgError("inCollection: key[1] is not kcollectionitem"); + if (!Name.empty()) + result &= (keys[0]->value() == Name); + return result; +} + + +const strvector & +mgSelection::keychoice (const unsigned int level) +{ + m_keychoice.clear (); + if (level > keys.size ()) + return m_keychoice; + map < string, keyfield * >::iterator it; + map < string, keyfield * > possible_keys; + for (it = all_keys.begin (); it != all_keys.end (); it++) + { + keyfield*f = (*it).second; + if (keycounts.find (f->choice ()) == keycounts.end ()) + { + keycounts[f->choice ()] = exec_count (f->KeyCountquery ()); + } + unsigned int i = keycounts[f->choice ()]; + if ((&(*f) != &kcollection) && (&(*f) != &kcollectionitem) && (i < 2)) + ; + else + possible_keys[string(tr(f->choice ().c_str()))] = &(*f); + } + + for (it = possible_keys.begin (); it != possible_keys.end (); it++) + { + keyfield *k = (*it).second; + if (level != 0 && k == &kcollection) + continue; + if (level != 1 && k == &kcollectionitem) + continue; + if (level == 1 && keys[0] != &kcollection && k == &kcollectionitem) + continue; + if (level == 1 && keys[0] == &kcollection && k != &kcollectionitem) + continue; + if (level > 1 && keys[0] == &kcollection) + break; + if (k == &kdecade && UsedBefore (&kyear, level)) + continue; + if (!UsedBefore (k, level)) + m_keychoice.push_back (string(tr((*it).second->choice ().c_str()))); + } + return m_keychoice; +} + + +void mgSelection::DumpState(mgValmap& nv) +{ + nv.put("Host",m_Host); + nv.put("User",m_User); + nv.put("Password",m_Password); + nv.put("FallThrough",m_fall_through); + nv.put("ShuffleMode",int(m_shuffle_mode)); + nv.put("LoopMode",int(m_loop_mode)); + nv.put("Directory",m_Directory); + nv.put("ToplevelDir",m_ToplevelDir); + nv.put("Level",int(m_level)); + for (unsigned int i=0;i<keys.size();i++) + { + char *n; + asprintf(&n,"Keys.%d.Choice",i); + nv.put(n,keys[i]->choice()); + asprintf(&n,"Keys.%d.Filter",i); + nv.put(n,keys[i]->filter()); + if (i<m_level) { + asprintf(&n,"Keys.%d.Position",i); + nv.put(n,m_position[i]); + } + } + nv.put("TrackId",m_trackid); + if (m_level == keys.size ()) + nv.put("Position",m_tracks_position); + else + nv.put("Position",m_position[m_level]); + nv.put("TrackPosition",m_tracks_position); +} + +map <string, string> * +mgSelection::UsedKeyValues() +{ + map <string, string> *result = new map<string, string>; + for (unsigned int idx = 0 ; idx < level() ; idx++) + { + (*result)[keys[idx]->choice()] = keys[idx]->value(); + } + if (level() < keys.size()-1) + { + string ch = keys[level()]->choice(); + (*result)[ch] = getCurrentValue(); + } + return result; +} @@ -0,0 +1,1071 @@ +/*! + * \file mg_db.h + * \brief A database interface to the GiantDisc + * + * \version $Revision: 1.0 $ + * \date $Date: 2004-12-07 10:10:35 +0200 (Tue, 07 Dec 2004) $ + * \author Wolfgang Rohdewald + * \author Responsible author: $Author: wr $ + * + */ + +#ifndef _DB_H +#define _DB_H +#include <stdlib.h> +#include <mysql/mysql.h> +#include <string> +#include <list> +#include <vector> +#include <map> +#include <iostream> +#include <istream> +#include <sstream> +#include <ostream> +#include <i18n.h> +using namespace std; + +#include "mg_tools.h" + +typedef vector<string> strvector; + +//! \brief a map for reading / writing configuration data. +class mgValmap : public map<string,string> { + private: + const char *m_key; + public: + /*! \brief constructor + * \param key all names will be prefixed with key. + */ + mgValmap(const char *key); + //! \brief read from file + void Read(FILE *f); + //! \brief write to file + void Write(FILE *f); + //! \brief enter a string value + void put(const char*name, string value); + //! \brief enter a C string value + void put(const char*name, const char* value); + //! \brief enter a long value + void put(const char*name, long value); + //! \brief enter a int value + void put(const char*name, int value); + //! \brief enter a unsigned int value + void put(const char*name, unsigned int value); + //! \brief enter a bool value + void put(const char*name, bool value); + //! \brief return a string + string getstr(const char* name) { + return (*this)[name]; + } + //! \brief return a C string + bool getbool(const char* name) { + return (getstr(name)=="true"); + } + //! \brief return a long + long getlong(const char* name) { + return atol(getstr(name).c_str()); + } + //! \brief return an unsigned int + unsigned int getuint(const char* name) { + return (unsigned long)getlong(name); + } +}; + +static const string EMPTY = "XNICHTGESETZTX"; + +class mgSelection; + +//! \brief a generic keyfield +class keyfield +{ + public: + //! \brief set the owning selection. + void setOwner(mgSelection *owner) { selection = owner; } + + //! \brief default constructor + keyfield () + { + }; + + /*! \brief constructs a simple key field which only needs info from + * the tracks table. + * \param choice the internationalized name of this key field, e.g. "Jahr" + */ + keyfield (const string choice); + + //! \brief default destructor + virtual ~ keyfield () + { + }; + +/*! \brief assigns a new id and value to the key field. + * This also invalidates the cache of the owning mgSelection. + * \param id used for lookups in the data base + * \param value used for display + */ + void set (const string id, const string value); + +//! \brief helper function for streaming debug info + void writeAt (ostream &) const; + +//! \brief adds lookup data to a WHERE SQL statement + string restrict (string & result) const; + +//! \brief returns the internationalized name of this key field + string choice () const + { + return m_choice; + } + +/*! \brief returns the id of this key field. This is the string used + * for lookups in the data base, not for display + */ + string id () const + { + return m_id; + } + +/*! \brief returns the filter for this key field. + * \todo filters are not yet implemented but we already dump them in DumpState + */ + string filter () const + { + return m_filter; + } + +/*! \brief returns the value of this key field. This is the string used + * for the display. + */ + string value () const + { + return m_value; + } + + virtual string order() const + { + return valuefield (); + } + +//! \brief returns the name of the corresponding field in the tracks table + virtual string basefield () const { return ""; } + +//! \brief returns the name of the field to be shown in the selection list + virtual string valuefield () const + { + return basefield (); + } + +//! \brief returns the name of the identification field + virtual string idfield () const + { + return basefield (); + } + +/*! \brief returns the name of the field needed to count how many + * different values for this key field exist + */ + virtual string countfield () const + { + return basefield (); + } + +/*! \brief returns a join clause needed for the composition of + * a WHERE statement + */ + virtual string join () const; + +/*! \brief returns a join clause needed for the composition of + * a WHERE statement especially for counting the number of items + */ + virtual string countjoin () const + { + return join (); + } + +/*! \brief if true, the WHERE clause should also return values from + * join tables + */ + bool lookup; + +//! \brief returns a SQL query command for counting different key +//values + string KeyCountquery (); + + protected: +//! \brief the owning selection. + mgSelection* selection; + //! \brief the english name for this key field + string m_choice; + //! \brief used for lookup in the data base + string m_id; + //! \brief used for OSD display + string m_value; + //! \brief an SQL restriction like 'tracks.year=1982' + string m_filter; + /*! \brief should be defined as true if we need to join another + * table for getting user friendly values (like the name of a genre) + */ + virtual bool need_join () const; + //! \brief escape the string as needed for calls to mysql + string sql_string(const string s) const; +}; + + +//! \brief orders by collection +class collectionkeyfield:public keyfield +{ + public: + collectionkeyfield ():keyfield ("Collection") + { + } + string basefield () const + { + return "playlist.id"; + } + string valuefield () const + { + return "playlist.title"; + } +/* this join() would ensure that empty collections be suppressed. But we + * want them all. so we don't need the join + */ + string join () const + { + return ""; + } +}; + +//! \brief orders by position in collection +class collectionitemkeyfield:public keyfield +{ + public: + collectionitemkeyfield ():keyfield ("Collection item") + { + } + string basefield () const + { + return "playlistitem.tracknumber"; + } + string valuefield () const + { + return "tracks.title"; + } + string order () const + { + return basefield (); + } + string join () const + { + return + "tracks.id=playlistitem.trackid and playlist.id=playlistitem.playlist"; + } +}; + +//! \brief orders by album.title +class albumkeyfield:public keyfield +{ + public: + albumkeyfield ():keyfield ("Album") + { + } + string basefield () const + { + return "tracks.sourceid"; + } + string valuefield () const + { + return "album.title"; + } + string idfield () const + { + return "album.title"; + } + string countfield () const + { + return "album.title"; + } + string join () const + { + return "tracks.sourceid=album.cddbid"; + } + protected: + //!brief we always need to join table album + bool need_join () const + { + return true; + }; +}; + +//! \brief orders by genre1 +class genre1keyfield:public keyfield +{ + public: + genre1keyfield ():keyfield ("Genre 1") + { + } + string basefield () const + { + return "tracks.genre1"; + } + string valuefield () const + { + return "genre.genre"; + } + string idfield () const + { + return "genre.id"; + } +}; + +//! \brief orders by genre2 +class genre2keyfield:public keyfield +{ + public: + genre2keyfield ():keyfield ("Genre 2") + { + } + string basefield () const + { + return "tracks.genre2"; + } + string valuefield () const + { + return "genre.genre"; + } + string idfield () const + { + return "genre.id"; + } +}; + +//! \brief orders by language +class langkeyfield:public keyfield +{ + public: + langkeyfield ():keyfield ("Language") + { + } + string basefield () const + { + return "tracks.lang"; + } + string valuefield () const + { + return "language.language"; + } + string idfield () const + { + return "language.id"; + } +}; + +//! \brief orders by tracks.artist +class artistkeyfield:public keyfield +{ + public: + artistkeyfield ():keyfield ("Artist") + { + } + string basefield () const + { + return "tracks.artist"; + } +}; + +//! \brief orders by tracks.rating +class ratingkeyfield:public keyfield +{ + public: + ratingkeyfield ():keyfield ("Rating") + { + } + string basefield () const + { + return "tracks.rating"; + } +}; + +//! \brief orders by tracks.year +class yearkeyfield:public keyfield +{ + public: + yearkeyfield ():keyfield ("Year") + { + } + string basefield () const + { + return "tracks.year"; + } +}; + +//! \brief orders by tracks.title +class titlekeyfield:public keyfield +{ + public: + titlekeyfield ():keyfield ("Title") + { + } + string basefield () const + { + return "tracks.title"; + } +}; + +//! \brief orders by tracks.tracknb and tracks.title +class trackkeyfield:public keyfield +{ + public: + trackkeyfield ():keyfield ("Track") + { + } + string basefield () const + { + return "tracks.tracknb"; + } + string valuefield () const + { + return + "concat(" + "if(tracks.tracknb>0," + "concat(" + "if(tracks.tracknb<10,' ','')," + "tracks.tracknb," + "' '" + "),''" + ")," + "tracks.title)"; + } +}; + +//! \brief orders by decade (deduced from tracks.year) +class decadekeyfield:public keyfield +{ + public: + decadekeyfield ():keyfield ("Decade") + { + } + string basefield () const + { + return "substring(convert(10 * floor(tracks.year/10), char),3)"; + } +}; + +//! \brief represents a content item like an mp3 file +class mgContentItem +{ + public: + mgContentItem () + { + } + //! \brief copy constructor + mgContentItem(const mgContentItem* c); + + //! \brief construct an item from an SQL row + mgContentItem (const MYSQL_ROW row, const string ToplevelDir); +//! \brief returns track id + long getId () const + { + return m_id; + } + +//! \brief returns title + string getTitle () const + { + return m_title; + } + +//! \brief returns filename + string getSourceFile () const + { + return m_mp3file; + } + +//! \brief returns artist + string getArtist () const + { + return m_artist; + } + +//! \brief returns the name of the album + string getAlbum (); + +//! \brief returns the name of genre 1 + string getGenre1 (); + +//! \brief returns the name of genre 2 + string getGenre2 (); + +//! \brief returns the name of genre 1 + string getGenre () + { + return getGenre1 (); + } + +//! \brief returns the bitrate + string getBitrate () const + { + return m_bitrate; + } + +//! \brief returns the file name of the album image + string getImageFile (); + +//! \brief returns year + int getYear () const + { + return m_year; + } + +//! \brief returns rating + int getRating () const + { + return m_rating; + } + +//! \brief returns duration + int getDuration () const + { + return m_duration; + } + +//! \brief returns samplerate + int getSampleRate () const + { + return m_samplerate; + } + +//! \brief returns # of channels + int getChannels () const + { + return m_channels; + } + private: + long m_id; + string m_title; + string m_mp3file; + string m_artist; + string m_albumtitle; + string m_genre1; + string m_genre2; + string m_bitrate; + int m_year; + int m_rating; + int m_duration; + int m_samplerate; + int m_channels; +}; + +/*! + * \brief the only interface to the database. + */ +class mgSelection +{ + private: + class mgSelStrings + { + friend class mgSelection; + private: + strvector strings; + mgSelection* m_sel; + void setOwner(mgSelection* sel); + public: + string& operator[](unsigned int idx); + size_t size(); + }; + + public: +/*! \brief define various ways to play music in random order + * \todo Party mode is not implemented, does same as SM_NORMAL + */ + enum ShuffleMode + { + SM_NONE, //!< \brief play normal sequence + SM_NORMAL, //!< \brief a shuffle with a fair distribution + SM_PARTY //!< \brief select the next few songs randomly, continue forever + }; + +//! \brief define various ways to play music in a neverending loop + enum LoopMode + { + LM_NONE, //!< \brief do not loop + LM_SINGLE, //!< \brief loop a single track + LM_FULL //!< \brief loop the whole track list + }; + +//! \brief escapes special characters + string sql_string(const string s) const; + +//! \brief the default constructor. Does not start a DB connection. + mgSelection(); + +/*! \brief the main constructor + * \param Host where the data base lives. If not localhost, TCP/IP is used. + * \param User if empty, the current user is used. + * \param Password no comment + * \param fall_through if TRUE: If enter() returns a choice + * containing only one item, that item is automatically entered. + * The analog happens with leave() + */ + mgSelection (const string Host, const string User = + "", const string Password = "", const bool fall_through = + false); + +/*! \brief a copy constructor. Does a deep copy. + * Some of the data base content will only be retrieved by the + * new mgSelection as needed, so some data base + * overhead is involved + */ + mgSelection (const mgSelection& s); +/*! \brief a copy constructor. Does a deep copy. + * Some of the data base content will only be retrieved by the + * new mgSelection as needed, so some data base + * overhead is involved + */ + mgSelection (const mgSelection* s); + +/*! \brief the assignment operator. Does a deep copy. + * Some of the data base content will only be retrieved by the + * new mgSelection as needed, so some data base + * overhead is involved + */ + const mgSelection& operator=(const mgSelection& s); + +//! \brief initializes from a map. + void InitFrom(mgValmap& nv); + +//! \brief the normal destructor + ~mgSelection (); + + //! \brief sets the top level directory where content is stored + void setToplevelDir(string ToplevelDir) { m_ToplevelDir = ToplevelDir; } + +/*! \brief represents all values for the current level. The result + * is cached in values, subsequent accesses to values only incur a + * small overhead for building the SQL WHERE command. The values will + * be reloaded when the SQL command changes + * \todo we should do more caching. The last 5 result sets should be cached. + */ + mgSelStrings values; + +/*! \brief defines a field to be used as key for selection + * + * \param level 0 is the top level + * \param name of the key field, internationalized. Possible values + * are defined by keychoice() + */ + void setKey (const unsigned int level, const string name); + +/*! \brief returns the name of a key + */ + string getKeyChoice (const unsigned int level) + { + return keys[level]->choice (); + } + //! \brief return the current value of this key + string getKeyValue (const unsigned int level) + { + return keys[level]->value (); + } + +//! \brief returns a map (new allocated) for all used key fields and their values + map<string,string> * UsedKeyValues(); + +//! \brief helper function for << operator (dumps debug info) + void writeAt (ostream &); + +/*! \brief returns FROM and WHERE clauses for the current state + * of the selection. + * \param want_trackinfo work in progress, should disappear I hope + */ + string where (bool want_trackinfo = false); + +//! \brief the number of music items currently selected + unsigned int count (); + +//! \brief the number of key fields used for the query + unsigned int size (); + +//! \brief the current position in the current level + unsigned int gotoPosition () + { + return gotoPosition (m_level); + } + +//! \brief the current position + unsigned int getPosition (unsigned int level) const; + + //! \brief go to the current position. If it does not exist, + // go to the nearest. + unsigned int gotoPosition (unsigned int level); + +//! \brief the current position in the tracks list + unsigned int getTrackPosition () const; + + //! \brief go to the current track position. If it does not exist, + // go to the nearest. + unsigned int gotoTrackPosition (); + +/*! \brief enter the the next higher level, go one up in the tree. + * If fall_through (see constructor) is set to true, and the + * level entered by enter() contains only one item, automatically + * goes down further until a level with more than one item is reached. + * \param position is the position in the current level that is to be expanded + * \return returns false if there is no further level + */ + bool enter (unsigned int position); + + /*! \brief like enter but if we are at the leaf level simply select + * the entry at position + */ + bool select (unsigned int position); + +/*! \brief enter the next higher level, expanding the current position. + * See also enter(unsigned int position) + */ + bool enter () + { + return enter (gotoPosition ()); + } + + /*! \brief like enter but if we are at the leaf level simply select + * the current entry + */ + bool select () + { + return select (gotoPosition ()); + } + +/*! \brief enter the next higher level, expanding the position holding a certain value + * \param value the position holding value will be expanded. + */ + bool enter (const string value) + { + return enter (valindex (value)); + } + + /*! \brief like enter but if we are at the leaf level simply select + * the current entry + */ + bool select (const string value) + { + return select (valindex(value)); + } + +/*! \brief leave the current level, go one up in the tree. + * If fall_through (see constructor) is set to true, and the + * level entered by leave() contains only one item, automatically + * goes up further until a level with more than one item is reached. + * \return returns false if there is no further upper level + */ + bool leave (); + +/*! \brief leave the current level, go up in the tree until + * target level is reached. + * If fall_through (see constructor) is set to true, and the + * level entered by leave() contains only one item, automatically + * goes up further until a level with more than one item is reached. + * \return returns false if there is no further upper level + */ + bool leave (const unsigned int target_level) + { + while (m_level>target_level) + if (!leave()) return false; + return true; + } + +//! \brief the current level in the tree + unsigned int level () const + { + return m_level; + } + +/*! \brief the possible choices for a keyfield in this level. + * keyfields already used in upper levels are no possible + * choices, neither are most keyfields if their usage would + * allow less than 2 choices. + */ + const strvector &keychoice (const unsigned int level); + +/*! \brief returns the current item from the value() list + */ + string getCurrentValue(); + + //! \brief true if the selection holds no items + bool empty(); + +/*! \brief returns detailled info about all selected tracks. + * The ordering is done only by the keyfield of the current level. + * This might have to be changed - suborder by keyfields of detail + * levels. This list is cached so several consequent calls mean no + * loss of performance. See value(), the same warning applies. + * \todo call this more seldom. See getNumTracks() + */ + const vector < mgContentItem > &tracks (); + +/*! \brief returns an item from the tracks() list + * \param position the position in the tracks() list + * \return returns NULL if position is out of range + */ + mgContentItem* getTrack (unsigned int position); + +/*! \brief returns the current item from the tracks() list + */ + mgContentItem* getCurrentTrack () + { + return getTrack (gotoTrackPosition()); + } + +/*! \brief toggles the shuffle mode thru all possible values. + * When a shuffle modus SM_NORMAL or SM_PARTY is selected, the + * order of the tracks in the track list will be randomly changed. + */ + ShuffleMode toggleShuffleMode (); + +//! \brief toggles the loop mode thru all possible values + LoopMode toggleLoopMode (); + +//! \brief returns the current shuffle mode + ShuffleMode getShuffleMode () const + { + return m_shuffle_mode; + } + +//! \brief sets the current shuffle mode + void setShuffleMode (const ShuffleMode shuffle_mode) + { + m_shuffle_mode = shuffle_mode; + } + +//! \brief returns the current loop mode + LoopMode getLoopMode () const + { + return m_loop_mode; + } + +//! \brief sets the current loop mode + void setLoopMode (const LoopMode loop_mode) + { + m_loop_mode = loop_mode; + } + +/*! \brief adds the whole current track list to a collection + * \param Name the name of the collection. If it does not yet exist, + * it will be created. + */ + unsigned int AddToCollection (const string Name); + +/*! \brief removes the whole current track from a the collection + * Remember - this selection can be configured to hold exactly + * one list, so this command can be used to clear a selected list. + * \param Name the name of the collection + */ + unsigned int RemoveFromCollection (const string Name); +//! \brief delete a collection + bool DeleteCollection (const string Name); +/*! \brief create a collection only if it does not yet exist. + * \return true only if it has been created. false if it already existed. + */ + bool CreateCollection(const string Name); + +//! \brief remove all items from the collection + void ClearCollection (const string Name); + +/*! generates an m3u file containing all tracks. The directory + * can be indicated by SetDirectory(). + * The file name will be built from the list name, slashes + * and spaces converted + */ + string exportM3U (); + +/*! \brief go to a position in the current level. If we are at the + * most detailled level this also sets the track position since + * they are identical. + * \param position the wanted position. If it is too big, go to the + * last existing position + * \return only if no position exists, false will be returned + */ + void setPosition (unsigned int position); + +/*! \brief go to the position with value in the current level + * \param value the value of the wanted position + */ + void setPosition (const string value) + { + setPosition (valindex (value)); + } + +/*! \brief go to a position in the track list + * \param position the wanted position. If it is too big, go to the + * last existing position + * \return only if no position exists, false will be returned + */ + void setTrack (unsigned int position); + +/*! \brief skip some tracks in the track list + * \return false if new position does not exist + */ + bool skipTracks (int step=1); + +/*! \brief skip forward by 1 in the track list + * \return false if new position does not exist + */ + bool skipFwd () + { + return skipTracks (+1); + } + +/*! \brief skip back by 1 in the track list + * \return false if new position does not exist + */ + bool skipBack () + { + return skipTracks (-1); + } + +//! \brief returns the sum of the durations of all tracks + unsigned long getLength (); + +/*! \brief returns the sum of the durations of completed tracks + * those are tracks before the current track position + */ + unsigned long getCompletedLength (); + +/*! returns the number of tracks in the track list + * \todo should not call tracks () which loads all track info. + * instead, only count the tracks. If the size differs from + * m_tracks.size(), invalidate m_tracks + */ + unsigned int getNumTracks () + { + return tracks ().size (); + } + +//! sets the directory for the storage of m3u file + void SetDirectory (const string directory) + { + m_Directory = directory; + } + +/*! returns the name of the current play list. If no play list is active, + * the name is built from the name of the key fields. + */ + string getListname (); + +/*! \brief true if this selection currently selects a list of collections + */ + bool isCollectionlist (); + + //! \brief true if we have entered a collection + bool inCollection(const string Name=""); + + /*! \brief dumps the entire state of this selection into a map, + * \param nv the values will be entered into this map + */ + void DumpState(mgValmap& nv); + + /*! \brief creates a new selection using saved definitions + * \param nv this map contains the saved definitions + */ + mgSelection(mgValmap& nv); + + //! \brief clear the cache, next access will reload from data base + void clearCache(); + + //! \todo soll sql_values() nur noch bei Bedarf bauen, also muessen + // alle Aenderungen, die Einfluss darauf haben, clearCache machen + void refreshValues(); + + //! \brief true if values and tracks need to be reloaded + bool cacheIsEmpty() + { + return (m_current_values=="" && m_current_tracks==""); + } + private: + void AddOrder(const string sql,list<string>& orderlist, const string item); + list < string > m_fromtables; //!< \brief part result from previous where() + string m_from; //!< \brief part result from previous where() + string m_where; //!< \brief part result from previous where() + bool m_fall_through; + vector < unsigned int >m_position; + unsigned int m_tracks_position; + ShuffleMode m_shuffle_mode; + LoopMode m_loop_mode; + MYSQL *m_db; + string m_Host; + string m_User; + string m_Password; + string m_ToplevelDir; + unsigned int m_level; + long m_trackid; + string m_current_values; + string m_current_tracks; + +//! \brief be careful when accessing this, see mgSelection::tracks() + vector < mgContentItem > m_tracks; + strvector m_ids; + strvector m_keychoice; + artistkeyfield kartist; + ratingkeyfield krating; + yearkeyfield kyear; + decadekeyfield kdecade; + albumkeyfield kalbum; + collectionkeyfield kcollection; + collectionitemkeyfield kcollectionitem; + genre1keyfield kgenre1; + genre2keyfield kgenre2; + langkeyfield klanguage; + titlekeyfield ktitle; + trackkeyfield ktrack; + map < string, keyfield * >all_keys; + map < string, keyfield * >trall_keys; + vector < keyfield * >keys; + bool UsedBefore (keyfield const *k, unsigned int level); + void InitSelection (); + void InitDatabase (); + void initkey (keyfield & f); + /*! \brief returns the SQL command for getting all values. + * For the leaf level, all values are returned. For upper + * levels, every distinct value is returned only once. + * This must be so for the leaf level because otherwise + * the value() entries do not correspond to the track() + * entries and the wrong tracks might be played. + */ + string sql_values (); + //! \todo das nach mgSelStrings verlagern + unsigned int valindex (const string val,const bool second_try=false); + string ListFilename (); + string m_Directory; + void loadgenres (); + MYSQL_RES *exec_sql (string query); + string get_col0 (string query); + + void InitFrom(const mgSelection* s); + +/*! \brief executes a query and returns the integer value from + * the first column in the first row. The query shold be a COUNT query + * returning only one row. + * \param query the SQL query to be executed + */ + unsigned long mgSelection::exec_count (string query); + + keyfield* findKey (const string name); + map < string, unsigned int > keycounts; +}; + +//! \brief streams debug info about a selection +ostream & operator<< (ostream &, mgSelection & s); + +//! \brief convert the shuffle mode into a string +// \return strings "SM_NONE" etc. +string toString (mgSelection::ShuffleMode); + +//! \brief same as toString but returns a C string +const char *toCString (mgSelection::ShuffleMode); + +//string toString(long int l); + +string itos (int i); +unsigned int randrange (const unsigned int high); + + +#endif // _DB_H diff --git a/mg_media.c b/mg_media.c deleted file mode 100644 index 2b25b73..0000000 --- a/mg_media.c +++ /dev/null @@ -1,453 +0,0 @@ -/*! \file mg_media.c - * \brief Top level access to media in vdr plugin muggle - * - * \version $Revision: 1.14 $ - * \date $Date$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author$ - */ - -/* makes sure we dont parse the same declarations twice */ -#include "mg_media.h" - -#include "mg_tools.h" -#include "mg_content_interface.h" - -#include "gd_content_interface.h" - -#include "vdr_setup.h" - - -//------------------------------------------------------------------- -// mgFilterSets -//------------------------------------------------------------------- -/*! - * \brief constructor - * - * constructor of the base class - */ - -mgFilterSets::mgFilterSets() -{ - // nothing to be done in the base class -} - -mgFilterSets::~mgFilterSets() -{ - std::vector<mgFilter*> *set; - for(std::vector<std::vector<mgFilter*>*>::iterator iter1 = m_sets.begin(); - iter1 != m_sets.end(); iter1++) - { - set = *iter1; - for(std::vector<mgFilter*>::iterator iter2 = set->begin(); - iter2 != set->end(); iter2++) - { - delete (*iter2); - } - set->clear(); - delete set; - } - m_sets.clear(); -} -/*! - ******************************************************************* - * \brief returns the number of available sets - ********************************************************************/ -int mgFilterSets::numSets() -{ - return m_sets.size(); -} - - /*! - ******************************************************************* - * \brief proceeds to the next one in a circular fashion - ********************************************************************/ -void mgFilterSets::nextSet() -{ - m_activeSetId++; - if(m_activeSetId >= (int) m_sets.size()) - { - m_activeSetId = 0; - } - m_activeSet = m_sets[m_activeSetId]; -} - // - -/*! - ******************************************************************* - * \brief activates a specific set by index - * - * \par n : index of the set to be activated - * - * If n is not a valid filter set, the first set (index 0 ) is activated - ********************************************************************/ -void mgFilterSets::select(int n) -{ - m_activeSetId = n ; - if(m_activeSetId >= (int) m_sets.size()) - { - m_activeSetId = 0; - } - m_activeSet = m_sets[m_activeSetId]; - -} - - /*! - ******************************************************************* - * \brief restores the default values for all filter values in the active set - ********************************************************************/ - void mgFilterSets::clear() -{ - for(std::vector<mgFilter*>::iterator iter = m_activeSet->begin(); - iter != m_activeSet->end(); iter++) - { - (*iter)->clear(); - } -} - /*! - ******************************************************************* - * \brief stores the current filter values - ********************************************************************/ - void mgFilterSets::accept() -{ - for(std::vector<mgFilter*>::iterator iter = m_activeSet->begin(); - iter != m_activeSet->end(); iter++) - { - (*iter)->store(); - } -} - -/*! - ******************************************************************* - * \brief returns the active filter set to the application - * - * the application may temporarily modify the filter values - * accept() needs to be called to memorize the changed values - ********************************************************************/ - std::vector<mgFilter*> *mgFilterSets::getFilters() -{ - for(std::vector<mgFilter*>::iterator iter = m_activeSet->begin(); - iter != m_activeSet->end(); iter++) - { - (*iter)->restore(); - } - return m_activeSet; -} - - -/*! - * \brief return title of the active filter set - */ -std::string mgFilterSets::getTitle() -{ - if(m_activeSetId < (int) m_titles.size()) - { - return m_titles[m_activeSetId]; - } - else - { - mgWarning("Implementation error: No title std::string for filter set %d", - m_activeSetId); - return "NO-TITLE"; - } -} - -/*! - * \class mgMedia - * - * \brief mein class to access content in the vdr plugin muggle - */ -mgMedia::mgMedia(contentType mediatype) -{ - int errval = 0; - m_filters = NULL; - m_mediatype = mediatype; - m_sql_filter = "1"; - m_defaultView = 1; - - // now initialize the database - mgDebug(1, "Media Type %sselected", getMediaTypeName().c_str()); - switch(m_mediatype) - { - case GD_MP3: - { - errval = GdInitDatabase(&m_db); - mgDebug(3, "Successfully connected to sql database %s", the_setup.DbName ); - } - } - if(errval < 0) - { - mgError("Error connecting to database\n"); - } - - mgDebug(3, "Initializing track filters"); - switch(m_mediatype) - { - case GD_MP3: - { - errval = GdInitDatabase( &m_db ); // TODO: why duplicate this? LVW - mgDebug(3, "Successfully conntected to sql database %s", the_setup.DbName ); - } - } -} - -mgMedia::~mgMedia() -{ - if( m_filters ) - { - delete m_filters; - } -} - -std::string mgMedia::getMediaTypeName() -{ - switch(m_mediatype) - { - case GD_MP3: - { - return "GiantDisc"; - } break; - } - mgError("implementation Error"); // we should never get here - return ""; -} - -mgSelectionTreeNode* mgMedia::getSelectionRoot() -{ - switch(m_mediatype) - { - case GD_MP3: - return new GdTreeNode(m_db, m_defaultView, m_sql_filter); - } - mgError("implementation Error"); // we should never get here - return NULL; -} - -mgPlaylist* mgMedia::createTemporaryPlaylist() -{ - std::string tmpname = "current"; - return loadPlaylist( tmpname ); -} - -mgPlaylist* mgMedia::loadPlaylist(std::string name) -{ - mgPlaylist *list; - switch( m_mediatype ) - { - case GD_MP3: - { - list = new GdPlaylist( name, m_db ); - list->setDisplayColumns(getDefaultCols()); - - return list; - } break; - } - mgError("Implementation error: Unknown media type"); // we should never get here - return NULL; -} - -/*! - * \brief Obtain a list of stored playlists - */ -std::vector<std::string> *mgMedia::getStoredPlaylists() -{ - switch(m_mediatype) - { - case GD_MP3: - { - return GdGetStoredPlaylists( m_db ); - } break; - } - mgError("implementation Error"); // we should never get here - return new std::vector<std::string>(); -} - -/*! - * \brief obtain the indices of columns which are presented by default - */ -std::vector<int> mgMedia::getDefaultCols() -{ - std::vector<int> cols; - switch(m_mediatype) - { - case GD_MP3: - { - cols.push_back(1); // artist - cols.push_back(0); // track - - return cols; - } break; - } - mgError("implementation Error"); // we should never get here - - return cols; -} - -/*! - * \brief - */ -mgTracklist* mgMedia::getTracks() -{ - mgTracklist *tracks; - switch(m_mediatype) - { - case GD_MP3: - tracks = new GdTracklist(m_db, m_sql_filter); - tracks->setDisplayColumns(getDefaultCols()); - return tracks; - } - mgError("implementation Error"); // we should never get here - - return NULL; -} - -/*! - * \brief creates FiliterSetObject for the selected media type - * and activates set n (if available) - */ -void mgMedia::initFilterSet(int num) -{ - switch(m_mediatype) - { - case GD_MP3: - { - m_filters = new gdFilterSets(); - m_filters->select(num); - } break; - } -} - -/*! - ******************************************************************* - * \brief returns pointer to the activen filter set to be modified by the osd - * - * Note: Modifications become only effective by calling applyActiveFilter() - ********************************************************************/ -std::vector<mgFilter*> *mgMedia::getActiveFilters() -{ - if(!m_filters) - { - mgError("ImplementationError: getActiveFilters m_filters == NULL"); - } - return m_filters->getFilters(); -} - -/*! - * \brief returns title of the active filter set - */ -std::string mgMedia::getActiveFilterTitle() -{ - - switch(m_mediatype) - { - case GD_MP3: - { - if( !m_filters ) - { - mgError("ImplementationError:getActiveFilterTitle m_filters == NULL"); - } - return m_filters->getTitle(); - } break; - } - return ""; -} - -/*! - * \brief proceeds to the next filter set in a cirlucar fashion - */ -void mgMedia::nextFilterSet() -{ - if(!m_filters) - { - mgError("ImplementationError: nextFilterSet m_filters == NULL"); - } - m_filters->nextSet(); -} - -/*! - * \brief clears the current filter values and restores defaults - */ -void mgMedia::clearActiveFilter() -{ - if( !m_filters ) - { - mgError("ImplementationError: clearActiveFilter m_filters == NULL"); - } - m_filters->clear(); -} - -/*! - * \brief Applies the active filter set and returns a root node for the - * selection in the default view for this filter set - */ -mgSelectionTreeNode* mgMedia::applyActiveFilter() -{ - int view; - GdTreeNode* node; - - switch(m_mediatype) - { - case GD_MP3: - { - if(!m_filters) - { - mgError("ImplementationError: applyActiveFilter() m_filters == NULL"); - } - m_filters->accept(); - m_sql_filter = m_filters->computeRestriction(&view); - node = new GdTreeNode(m_db, view, m_sql_filter); - node->expand(); - return node->getChildren()[0]; - } break; - } - return NULL; -} - - -/* -------------------- begin CVS log --------------------------------- - * $Log: mg_media.c,v $ - * Revision 1.14 2004/07/29 06:17:40 lvw - * Added todo entries - * - * Revision 1.13 2004/05/28 15:29:18 lvw - * Merged player branch back on HEAD branch. - * - * Revision 1.12 2004/02/23 16:30:58 RaK - * - album search error because of i18n corrected - * - * Revision 1.11 2004/02/12 09:15:07 LarsAC - * Moved filter classes into separate files - * - * Revision 1.10.2.3 2004/05/25 00:10:45 lvw - * Code cleanup and added use of real database source files - * - * Revision 1.10.2.2 2004/03/14 17:57:30 lvw - * Linked against libmad. Introduced config options into code. - * - * Revision 1.10.2.1 2004/03/02 07:05:50 lvw - * Initial adaptations from MP3 plugin added (untested) - * - * Revision 1.12 2004/02/23 16:30:58 RaK - * - album search error because of i18n corrected - * - * Revision 1.11 2004/02/12 09:15:07 LarsAC - * Moved filter classes into separate files - * - * Revision 1.10 2004/02/10 23:47:23 RaK - * - views konsitent gemacht. siehe FROMJOIN - * - isLeafNode angepasst fuer neue views 4,5,100,101 - * - like '%abba%' eingebaut - * - filter ist default mit abba gefuellt, zum leichteren testen. - * - search results werden jetzt gleich im ROOT expanded - * - * Revision 1.9 2004/02/09 19:27:52 MountainMan - * filter set implemented - * - * Revision 1.8 2004/02/02 23:33:41 MountainMan - * impementation of gdTrackFilters - * - * Revision 1.7 2004/02/02 22:48:04 MountainMan - * added CVS $Log - * - * --------------------- end CVS log ---------------------------------- - */ diff --git a/mg_media.h b/mg_media.h deleted file mode 100644 index 90a43f2..0000000 --- a/mg_media.h +++ /dev/null @@ -1,209 +0,0 @@ -/*! - * \file mgmedia.h - * \brief Top level access to media in vdr plugin muggle - * - * \version $Revision: 1.11 $ - * \date $Date$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author$ - */ - -// makes sure we dont use parse the same declarations twice -#ifndef _MG_MEDIA_H -#define _MG_MEDIA_H - -#include <string> -#include <vector> - -#include <mysql/mysql.h> - -class mgPlaylist; -class mgTracklist; -class mgSelectionTreeNode; -class mgFilter; -class mgFilterSets; - -/*! - * \class mgFilterSets - * - * Represents one or several sets of filters to set and memorize search constraint - */ -class mgFilterSets -{ - protected: - int m_activeSetId; - std::vector<mgFilter*> *m_activeSet; // pointer to the active filter set - - // stores name-value pairs, even if a different set is active - std::vector< std::vector<mgFilter*>*> m_sets; - - // stores the titles for all filters - std::vector<std::string> m_titles; - - public: - - /*! - * \brief a constructor - * - * constracts a number >=1 of filter sets the first set (index 0 ) is active by default - */ - mgFilterSets(); - - /*! - * \brief the destructor - */ - virtual ~mgFilterSets(); - - /*! - * \brief returns the number of available sets - */ - int numSets(); - - /*! - * \brief proceeds to the next one in a circular fashion - */ - void nextSet(); - - /*! - * \brief activates a specific set by index - */ - void select(int n); - - /*! - * \brief restore empty state - * - * Restores the default values for all filter values in the active set - * normally, the default values represent 'no restrictions' - */ - virtual void clear(); - - /*! - * \brief stores the current filter values - */ - void accept(); - - /*! - * \brief returns the active set to the application - * - * The application may temporarily modify the filter values - * accept() needs to be called to memorize the changed values - */ - std::vector<mgFilter*> *getFilters(); - - /*! - * \brief compute restrictions - * - * computes the (e.g. sql-) restrictions specified by the active filter set - * and returns the index of the appropriate defualt view in viewPrt - */ - virtual std::string computeRestriction(int *viewPrt) = 0; - - /*! - * \brief returns title of active filter set - */ - std::string getTitle(); -}; - - -/*! - * \class mgMedia - * - * \brief main class to access content in the vdr plugin muggle - * - * The constructor of this class should be the only point in the plugin, - * where the data type is explicitelymentioned. - * The class provides a set of objects that abstract from the data - * type and source - */ -class mgMedia -{ - - public: - typedef enum contentType - { - GD_MP3 - } contentType; - - private: - MYSQL m_db; - contentType m_mediatype; - std::string m_sql_filter; - int m_defaultView; - mgFilterSets *m_filters; - - public: - mgMedia(contentType mediatype); - ~mgMedia(); - - std::string getMediaTypeName(); - - mgSelectionTreeNode* getSelectionRoot(); - - /*! \brief playlist management */ - //@{ - mgPlaylist* createTemporaryPlaylist(); - mgPlaylist* loadPlaylist( std::string name ); - std::vector<std::string> *getStoredPlaylists(); - //@} - - std::vector<int> getDefaultCols(); - mgTracklist* getTracks(); - - // filter management - - void initFilterSet(int num=0); - // creates FiliterSetObject for the selected media type - // and activates set n (if available) - - std::vector<mgFilter*> *getActiveFilters(); - // returns pointer to the activen filter set to be modified by the osd - // Note: Modifications become only active by calling applyActiveFilter() - - std::string getActiveFilterTitle(); - - void nextFilterSet(); - // proceeds to the next filter set in a cirlucar fashion - - void clearActiveFilter(); - // clears the current filter values and restores defaults - - mgSelectionTreeNode *applyActiveFilter(); - // Applies the active filter set and returns a root node for the - // selection in the default view for this filter set - -}; - -/* -------------------- begin CVS log --------------------------------- - * $Log: mg_media.h,v $ - * Revision 1.11 2004/05/28 15:29:18 lvw - * Merged player branch back on HEAD branch. - * - * Revision 1.10 2004/02/12 09:15:07 LarsAC - * Moved filter classes into separate files - * - * Revision 1.9.2.2 2004/05/25 00:10:45 lvw - * Code cleanup and added use of real database source files - * - * Revision 1.9.2.1 2004/03/02 07:05:50 lvw - * Initial adaptations from MP3 plugin added (untested) - * - * Revision 1.10 2004/02/12 09:15:07 LarsAC - * Moved filter classes into separate files - * - * Revision 1.9 2004/02/09 22:07:44 RaK - * secound filter set (album search incl. special view #101 - * - * Revision 1.8 2004/02/09 19:27:52 MountainMan - * filter set implemented - * - * Revision 1.7 2004/02/02 23:33:41 MountainMan - * impementation of gdTrackFilters - * - * Revision 1.6 2004/02/02 22:48:04 MountainMan - * added CVS $Log - * - * - * --------------------- end CVS log ---------------------------------- - */ -#endif /* END _MG_MEDIA_H */ - diff --git a/mg_playlist.c b/mg_playlist.c deleted file mode 100644 index 5ead8f7..0000000 --- a/mg_playlist.c +++ /dev/null @@ -1,368 +0,0 @@ -/*! - * \file mg_playlist.c - * \brief defines functions to be executed on playlists for the vdr muggle plugin - * - * \version $Revision: 1.6 $ - * \date $Date$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author$ - * - * This file implements the class mgPlaylist which maintains a playlist - * and supports editing (e.g. adding or moving tracks), navigating it - * (e.g. obtaining arbitrary items or accessing them sequentially) or - * making it persistent in some database. - */ - -#include <stdio.h> -#include "mg_playlist.h" -#include "mg_tools.h" - -#include <vector> -#include <iostream> - -mgPlaylist::mgPlaylist() -{ - m_current_idx = 0; - - char *buffer; - asprintf( &buffer, "Playlist-%ld", random() ); - - m_listname = buffer; -} - -mgPlaylist::mgPlaylist(std::string listname) -{ - m_current_idx = 0; - m_listname = listname; -} - -/* ==== destructor ==== */ - -mgPlaylist::~mgPlaylist() -{ -} - -mgPlaylist::LoopMode mgPlaylist::toggleLoopMode( ) -{ - switch( m_loop_mode ) - { - case LM_NONE: - { - m_loop_mode = LM_SINGLE; - } break; - case LM_SINGLE: - { - m_loop_mode = LM_FULL; - } break; - case LM_FULL: - { - m_loop_mode = LM_NONE; - } break; - default: - { - m_loop_mode = LM_NONE; - } - } - - return m_loop_mode; -} - -mgPlaylist::ShuffleMode mgPlaylist::toggleShuffleMode( ) -{ - switch( m_shuffle_mode ) - { - case SM_NONE: - { - m_shuffle_mode = SM_NORMAL; - } break; - case SM_NORMAL: - { - m_shuffle_mode = SM_PARTY; - } break; - case SM_PARTY: - { - m_shuffle_mode = SM_NONE; - } break; - default: - { - m_shuffle_mode = SM_NONE; - } - } - - return m_shuffle_mode; -} - -void mgPlaylist::initialize() -{ - m_current_idx = 0; - m_loop_mode = mgPlaylist::LM_NONE; - m_shuffle_mode = mgPlaylist::SM_NONE; -} - -/* ==== add/remove tracks ==== */ - -/* adds a song at the end of the playlist */ -void mgPlaylist::append(mgContentItem* item) -{ - m_list.push_back(item); -} - -/* append a list of tracks at the end of the playlist */ -void mgPlaylist::appendList( std::vector<mgContentItem*> *tracks ) -{ - std::vector<mgContentItem*>::iterator iter; - - mgDebug( 3, "Adding %d tracks to the playlist", tracks->size() ); - - for( iter = tracks->begin(); iter != tracks->end(); iter++ ) - { - m_list.push_back(*iter); - } - - // TODO: why is this vector cleared? shouldn't the caller take care of this? LVW - tracks->clear(); -} - -/* add a song after 'position' */ -void mgPlaylist::insert( mgContentItem* item, unsigned int position ) -{ - if( position >= m_list.size() ) - { - m_list.push_back(item); - } - else - { - m_list.insert( m_list.begin() + position, item ); - } -} - -bool mgPlaylist::remove( unsigned pos ) -{ - bool result = false; - - if( pos != m_current_idx ) - { - result = mgTracklist::remove( pos ); - - if( result && pos < m_current_idx ) - { - m_current_idx --; - } - } - - return result; -} - -void mgPlaylist::clear() -{ - // TODO: who takes care of memory allocation/deallocation of mgItems? - - std::vector<mgContentItem*>::iterator iter; - - for( iter = m_list.begin(); iter != m_list.end(); iter++ ) - { // delete each item in the list - delete *iter; - } - - // finally clear the list itself - m_list.clear(); - - // reset index - m_current_idx = 0; -} - -void mgPlaylist::move( unsigned from, unsigned to ) -{ - std::vector<mgContentItem*>::iterator from_iter = m_list.begin() + from; - std::vector<mgContentItem*>::iterator to_iter = m_list.begin() + to; - - m_list.insert( to_iter, *from_iter); - m_list.erase( from_iter ); - - if( from < m_current_idx ) - { - m_current_idx--; - } - if( to < m_current_idx ) - { - m_current_idx++; - } -} - -std::string mgPlaylist::getListname() -{ - return m_listname; -} - -void mgPlaylist::setListname(std::string name) -{ - m_listname = name; -} - -// returns current index in the playlist -unsigned mgPlaylist::getIndex() const -{ - return m_current_idx; -} - -unsigned long mgPlaylist::getCompletedLength() -{ - unsigned long result = 0; - - std::vector<mgContentItem*>::iterator iter; - for( iter = m_list.begin(); iter != m_list.begin() + m_current_idx; iter++ ) - { // each item in the list - result += (*iter)->getLength(); - } - - return result; -} - -// returns the current item of the list -mgContentItem* mgPlaylist::getCurrent() -{ - mgContentItem *result; - - if( 0 <= m_current_idx && m_current_idx < m_list.size() ) - { - result = *( m_list.begin() + m_current_idx ); - } - else - { - result = &(mgContentItem::UNDEFINED); - } - - return result; -} - -// skip to the nth track from the playlist -bool mgPlaylist::gotoPosition(unsigned int position) -{ - bool result = false; - - if( position < m_list.size() ) - { - m_current_idx = position; - result = true; - } - - return result; -} - -// proceeds to the next item -bool mgPlaylist::skipFwd() -{ - bool result = false; - - if( m_loop_mode == mgPlaylist::LM_SINGLE ) - { - result = true; - } - else - { - if( m_current_idx + 1 < m_list.size() ) // unless loop mode - { - m_current_idx ++; - result = true; - } - else - { - if( m_loop_mode == mgPlaylist::LM_FULL ) - { - m_current_idx = 0; - result = true; - } - else - { - result = false; - } - } - } - - // if we are already at the end -- just do nothing unless in loop mode - return result; -} - -// goes back to the previous item -bool mgPlaylist::skipBack() -{ - bool result = false; - - if( m_loop_mode == mgPlaylist::LM_SINGLE ) - { - result = true; - } - else - { - if( m_current_idx > 0 ) - { - m_current_idx --; - result = true; - } - else - { - if( m_loop_mode == mgPlaylist::LM_FULL ) - { - m_current_idx = m_list.size() -1; - result = true; - } - else - { - result = false; - } - } - } - // if we are at the beginning -- just do nothing unless in loop mode - return result; -} - -// get next track, do not update data structures -mgContentItem* mgPlaylist::sneakNext() -{ - mgContentItem* result; - - // TODO: a bug? - if( m_current_idx + 1 <= m_list.size() ) // unless loop mode - { - result = *(m_list.begin() + m_current_idx + 1); - } - else - { - if( m_loop_mode == mgPlaylist::LM_FULL ) - { - result = *(m_list.begin()); - } - else - { - return &(mgContentItem::UNDEFINED); - } - } - - return result; -} - -bool mgPlaylist::exportM3U( std::string m3u_file ) -{ - std::vector<mgContentItem*>::iterator iter; - bool result = true; - - // open a file for writing - FILE *listfile = fopen( m3u_file.c_str(), "w" ); - - if( !listfile ) - { - return false; - } - - fprintf( listfile, "#EXTM3U" ); - - for( iter = m_list.begin(); iter != m_list.end(); iter++ ) - { // each item in the list - fprintf( listfile, "#EXTINF:%d,%s\n", (*iter)->getLength(), (*iter)->getLabel().c_str() ); - fprintf( listfile, "%s", (*iter)->getSourceFile().c_str() ); - } - - fclose( listfile ); - - return result; -} diff --git a/mg_playlist.h b/mg_playlist.h deleted file mode 100644 index 060ebfa..0000000 --- a/mg_playlist.h +++ /dev/null @@ -1,209 +0,0 @@ -/*! - * \file mg_playlist.c - * \brief defines functions to be executed on playlists for the vdr muggle plugin - * - * \version $Revision: 1.6 $ - * \date $Date$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner - * \author Responsible author: $Author$ - * - * This file implements the class mgPlaylist which maintains a playlist - * and supports editing (e.g. adding or moving tracks), navigating it - * (e.g. obtaining arbitrary items or accessing them sequentially) or - * making it persistent in some database. - */ -#ifndef __MG_PLAYLIST -#define __MG_PLAYLIST - -#include <string> - -#include "mg_content_interface.h" - -/*! - * \class mgPlaylist - * - * \brief Represents a generic playlist, i.e. an ordered collection of tracks - * Derived classes may take care of specifics of certain media types - * - * \todo Are loop mode, shuffle mode, and party mode implemented here? - */ -class mgPlaylist : public mgTracklist -{ - -public: - - //! \brief define various ways to play music in random order - enum ShuffleMode - { - SM_NONE, //!< \brief play normal sequence - SM_NORMAL, //!< \brief a shuffle with a fair distribution - SM_PARTY //!< \brief select the next few songs randomly, continue forever - }; - - //! \brief define various ways to play music in a neverending loop - enum LoopMode - { - LM_NONE, //!< \brief do not loop - LM_SINGLE, //!< \brief loop a single track - LM_FULL //!< \brief loop the whole playlist - }; - - //! \brief object construction and destruction - //@{ - - //! \brief the default constructor (random listname) - mgPlaylist(); - - /*! \brief constructor with a specified listname - * - * \param listname - initial name of the list - */ - mgPlaylist( std::string listname ); - - void initialize(); - - //! \brief the destructor - virtual ~mgPlaylist(); - - //@} - - //! \brief control behavior - //@{ - - //! \brief toggle the loop mode. - LoopMode toggleLoopMode( ); - - //! \brief toggle the shuffle mode. - ShuffleMode toggleShuffleMode( ); - - //! \brief report the loop mode. - LoopMode getLoopMode( ) const { return m_loop_mode; } - - //! \brief report the shuffle mode. - ShuffleMode getShuffleMode( ) const { return m_shuffle_mode; } - //@} - - //! \brief modify playlist items - //@{ - /*! \brief adds a song at the end of the playlist - * - * \param item - the item to be appended - */ - virtual void append(mgContentItem* item); - - /*! \brief add a vector of songs at the end of the playlist - * - * \param tracks - the vector of tracks to be added - */ - virtual void appendList(std::vector<mgContentItem*> *tracks); - - /*! \brief add a song after a specified position - * - * Might be merged with append by using a default argument for the position - * - * \param item - the item to be added - * \param position - the position where the item should be added - */ - virtual void insert(mgContentItem* item, unsigned int position); - - //! \brief clear all tracks - virtual void clear(); - - /*! \brief move tracks within playlist - * - * \param from - the item of the index to be moved - * \param to - the target index of the item - */ - void move( unsigned from, unsigned to ); - - /*! \brief remove a track from the playlist - * - * \param pos - the index of the track to be removed - */ - bool remove( unsigned pos ); - - //@} - - //! \brief obtain the listname - std::string getListname() ; - - /*! \brief set the listname - * - * \param name - the new name of this list - */ - virtual void setListname(std::string name); - - //! \brief access playlist items - //@{ - - //! \brief returns current index in the playlist - unsigned getIndex() const; - - //! \brief make playlist persistent - virtual bool storePlaylist() = 0; - - //! \brief make playlist persistent under a different name - virtual bool storeAs( std::string name ) = 0; - - //! \brief obtain length of content already played - unsigned long getCompletedLength(); - - //! \brief export the playlist in m3u format - virtual bool exportM3U( std::string m3u_file ); - - /*! - * \brief returns the current item of the list - * - * \todo Return null in case of an empty list or invalid index - */ - virtual mgContentItem* getCurrent(); - - /*! \brief returns the nth track from the playlist - * - * \param position - the position to skip to - * \return true if position was okay and changed, false otherwise - */ - virtual bool gotoPosition(unsigned int position); - - /*! - * \brief proceeds to the next item - * - * \return true if position was okay and changed, false otherwise - * \todo Handle play modes - */ - virtual bool skipFwd(); - - /*! - * \brief goes back to the previous item - * - * \return true if position was okay and changed, false otherwise - * \todo Handle play modes - */ - virtual bool skipBack(); - - //! \brief obtain the next item without skipping the current position - virtual mgContentItem* sneakNext(); - //@} - -private: - - //! \brief current index in the playlist - // TODO: should be a property of the player? - unsigned m_current_idx; - - //! \brief the current loop mode - LoopMode m_loop_mode; - - //! \brief the current shuffle mode - ShuffleMode m_shuffle_mode; - -protected: - - // TODO: Why not make these private? Subclasses should use access functions. LVW - - //! \brief the name of the playlist - std::string m_listname; - -}; - -#endif @@ -1,4 +1,4 @@ -/*! +/*! * \file mg_tools.c * \brief A few util functions for standalone and plugin messaging for the vdr muggle plugindatabase * @@ -12,126 +12,116 @@ /*extern "C" {*/ - #include <stdarg.h> - #include <stdio.h> +#include <stdarg.h> +#include <stdio.h> /*} -*/ + */ #include <stdlib.h> +//! \brief buffer for messages #define MAX_BUFLEN 2048 -#define MAX_QUERY_BUFLEN 2048 static char buffer[MAX_BUFLEN]; -static char querybuf[MAX_QUERY_BUFLEN]; -static int DEBUG_LEVEL=3; +static int DEBUG_LEVEL = 3; -void mgSetDebugLevel(int new_level) +void +mgSetDebugLevel (int new_level) { - DEBUG_LEVEL = new_level; + DEBUG_LEVEL = new_level; } -void mgDebug(int level, const char *fmt, ...) + +void +mgDebug (int level, const char *fmt, ...) { - - va_list ap; - if(level <= DEBUG_LEVEL) - { - va_start(ap, fmt); - - vsnprintf(buffer, MAX_BUFLEN-1, fmt, ap); - if(STANDALONE) - { - fprintf(stderr, "dbg %d: %s\n", level, buffer); - } - else + + va_list ap; + if (level <= DEBUG_LEVEL) { + va_start (ap, fmt); + + vsnprintf (buffer, MAX_BUFLEN - 1, fmt, ap); + if (STANDALONE) + { + fprintf (stderr, "dbg %d: %s\n", level, buffer); + } + else + { #if !STANDALONE - isyslog( "%s\n", buffer); + isyslog ("%s\n", buffer); #endif + } } - } - va_end(ap); + va_end (ap); } -void mgDebug( const char *fmt, ... ) + +void +mgDebug (const char *fmt, ...) { - va_list ap; - va_start( ap, fmt ); - mgDebug( 1, fmt, ap ); + va_list ap; + va_start (ap, fmt); + mgDebug (1, fmt, ap); } -void mgWarning(const char *fmt, ...) +void +mgWarning (const char *fmt, ...) { - - va_list ap; - va_start(ap, fmt); - vsnprintf(buffer, MAX_BUFLEN-1, fmt, ap); - - if(STANDALONE) - { - fprintf(stderr, "warning: %s\n", buffer); - } - else + + va_list ap; + va_start (ap, fmt); + vsnprintf (buffer, MAX_BUFLEN - 1, fmt, ap); + + if (STANDALONE) + { + fprintf (stderr, "warning: %s\n", buffer); + } + else { #if !STANDALONE - isyslog( "Warning: %s\n", buffer); + isyslog ("Warning: %s\n", buffer); #endif } - - va_end(ap); + + va_end (ap); } -void mgError(const char *fmt, ...) + +void +mgError (const char *fmt, ...) { - - va_list ap; - va_start(ap, fmt); - vsnprintf(buffer, MAX_BUFLEN-1, fmt, ap); - if(STANDALONE) + va_list ap; + va_start (ap, fmt); + vsnprintf (buffer, MAX_BUFLEN - 1, fmt, ap); + + if (STANDALONE) { - fprintf(stderr, "Error: %s\n", buffer); - exit(1); + fprintf (stderr, "Error: %s\n", buffer); + exit (1); } - else + else { #if !STANDALONE - isyslog( "Error in Muggle: %s\n", buffer); + isyslog ("Error in Muggle: %s\n", buffer); #endif } - va_end(ap); + va_end (ap); } -MYSQL_RES* mgSqlReadQuery(MYSQL *db, const char *fmt, ...) -{ - va_list ap; - va_start( ap, fmt ); - vsnprintf( querybuf, MAX_QUERY_BUFLEN-1, fmt, ap ); - - if( mysql_query(db, querybuf) ) - { - mgError( "SQL error in MUGGLE:\n%s\n", querybuf ); - } - - MYSQL_RES *result = mysql_store_result(db); - - va_end(ap); - return result; -} -void mgSqlWriteQuery(MYSQL *db, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - vsnprintf(querybuf, MAX_QUERY_BUFLEN-1, fmt, ap); - - if( mysql_query(db, querybuf) ) - { - mgError( "SQL error in MUGGLE:\n%s\n", querybuf ); - } - - va_end(ap); +std::string trim(std::string const& source, char const* delims ) { + std::string result(source); + std::string::size_type index = result.find_last_not_of(delims); + if(index != std::string::npos) + result.erase(++index); + index = result.find_first_not_of(delims); + if(index != std::string::npos) + result.erase(0, index); + else + result.erase(); + return result; } @@ -1,4 +1,4 @@ -/*! \file muggle_tools.h +/*! \file mg_tools.h * \ingroup muggle * \brief A few utility functions for standalone and plugin messaging for the vdr muggle plugindatabase * @@ -6,7 +6,7 @@ * \date $Date$ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner * \author file owner: $Author$ - * + * */ /* makes sure we don't use the same declarations twice */ @@ -17,36 +17,21 @@ #include <string> #include <mysql.h> -#define STANDALONE 1 // what's this? - +#define STANDALONE 1 // what's this? -/*! \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 +/*! + * \brief Logging utilities * - * \todo Could be a member of mgDatabase? - */ -void mgSqlWriteQuery( MYSQL *db, const char *fmt, ... ); - - -/*! - * \brief Logging utilities - * * \todo these could be static members in the mgLog class * \todo code of these functions should be compiled conditionally */ //@{ -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, ...); +void mgSetDebugLevel (int new_level); +void mgDebug (int level, const char *fmt, ...); +void mgDebug (const char *fmt, ...); +void mgWarning (const char *fmt, ...); +//! \todo mgError should display the message on the OSD. How? +void mgError (const char *fmt, ...); //@} #ifdef DEBUG @@ -63,38 +48,40 @@ void mgError(const char *fmt, ...); /*! \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: - enum - { - LOG, WARNING, ERROR, FATAL - } mgLogLevel; + public: + enum + { + LOG, WARNING, ERROR, FATAL + } mgLogLevel; - std::ostream& getStream() - { - return std::cout; - } - - mgLog( std::string methodname ) : m_methodname( methodname ) - { - getStream() << m_methodname << " entered" << std::endl; - }; + std::ostream & getStream () + { + return std::cout; + } - ~mgLog() - { - getStream() << m_methodname << " terminated" << std::endl; - } + mgLog (std::string methodname):m_methodname (methodname) + { + getStream () << m_methodname << " entered" << std::endl; + }; - private: - - std::string m_methodname; + ~mgLog () + { + getStream () << m_methodname << " terminated" << std::endl; + } + + private: + + std::string m_methodname; }; -#endif /* _MUGGLE_TOOLS_H */ +std::string trim(std::string const& source, char const* delims = " \t\r\n"); + +#endif /* _MUGGLE_TOOLS_H */ @@ -1,10 +1,10 @@ -/*! +/*! * \file muggle.c * \brief Implements a plugin for browsing media libraries within VDR * * \version $Revision: 1.10 $ * \date $Date$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner, Wolfgang Rohdewald * \author Responsible author: $Author$ * * $Id$ @@ -15,222 +15,224 @@ #include "vdr_menu.h" #include "vdr_setup.h" #include "mg_tools.h" -#include "mg_playlist.h" -#include "mg_content_interface.h" -#include "mg_media.h" +#include "mg_db.h" #include "i18n.h" #include <getopt.h> #include <config.h> -static const char *VERSION = "0.0.8"; -static const char *DESCRIPTION = "Media juggle plugin for VDR"; -static const char *MAINMENUENTRY = "Muggle"; +static const char *VERSION = "0.1.0"; +static const char *DESCRIPTION = "Media juggle plugin for VDR"; +static const char *MAINMENUENTRY = "Muggle"; -static unsigned s_resume_idx = 0; - -const char* mgMuggle::Version(void) -{ - return VERSION; +const char * +mgMuggle::Version (void) +{ + return VERSION; } -const char* mgMuggle::Description(void) -{ - return DESCRIPTION; -} -const char* mgMuggle::MainMenuEntry(void) -{ - return MAINMENUENTRY; -} - -mgMuggle::mgMuggle(void) +const char * +mgMuggle::Description (void) { - // defaults for database arguments - the_setup.DbHost = strdup ("localhost"); - the_setup.DbSocket = NULL; - the_setup.DbPort = 0; - the_setup.DbName = strdup ("GiantDisc"); - the_setup.DbUser = strdup (""); - the_setup.DbPass = strdup (""); - the_setup.GdCompatibility = false; - the_setup.ToplevelDir = strdup ("/mnt/music/"); + return DESCRIPTION; } -mgMuggle::~mgMuggle() -{ - // Clean up after yourself! - // save current playlist as "current" and it will be retrieved at the next startup - if( m_playlist ) - { - m_playlist->storeAs( "current" ); - } -} -const char *mgMuggle::CommandLineHelp(void) +const char * +mgMuggle::MainMenuEntry (void) { - // Return a string that describes all known command line options. - return - " -h HHHH, --host=HHHH specify database host (default is localhost)\n" - " -s SSSS --socket=PATH specify database socket (default is TCP connection)\n" - " -n NNNN, --name=NNNN specify database name (overridden by -g)\n" - " -p PPPP, --port=PPPP specify port of database server (default is )\n" - " -u UUUU, --user=UUUU specify database user (default is )\n" - " -w WWWW, --password=WWWW specify database password (default is empty)\n" - " -t TTTT, --toplevel=TTTT specify toplevel directory for music (default is /mnt/music)\n" - " -g, --giantdisc enable full Giantdisc compatibility mode\n"; + return MAINMENUENTRY; } -bool mgMuggle::ProcessArgs(int argc, char *argv[]) -{ - mgDebug( 1, "mgMuggle::ProcessArgs" ); - - // Implement command line argument processing here if applicable. - static struct option long_options[] = - { - { "host", required_argument, NULL, 'h' }, - { "socket", required_argument, NULL, 's' }, - { "name", required_argument, NULL, 'n' }, - { "port", required_argument, NULL, 'p' }, - { "user", required_argument, NULL, 'u' }, - { "password", required_argument, NULL, 'w' }, - { "toplevel", required_argument, NULL, 't' }, - { "giantdisc", no_argument, NULL, 'g' }, - { NULL } - }; - - int c, option_index = 0; - while( ( c = getopt_long( argc, argv, "gh:s:n:p:t:u:w:", long_options, &option_index ) ) != -1 ) - { - switch (c) - { - case 'h': - { - the_setup.DbHost = strcpyrealloc (the_setup.DbHost, optarg); - } break; - case 's': - { - the_setup.DbSocket = strcpyrealloc (the_setup.DbSocket, optarg); - } break; - case 'n': - { - the_setup.DbName = strcpyrealloc (the_setup.DbName, optarg); - } break; - case 'p': - { - the_setup.DbPort = atoi( optarg ); - } break; - case 'u': - { - the_setup.DbUser = strcpyrealloc (the_setup.DbUser, optarg); - } break; - case 'w': - { - the_setup.DbPass = strcpyrealloc (the_setup.DbPass, optarg); - } break; - case 't': - { - if (optarg[strlen(optarg) - 1] != '/') - { - std::string res = std::string(optarg) + "/"; - the_setup.ToplevelDir = strdup( res.c_str() ); - } - else - { - the_setup.ToplevelDir = strcpyrealloc (the_setup.ToplevelDir, optarg); - } - } break; - case 'g': - { - the_setup.DbName = strcpyrealloc (the_setup.DbName, "GiantDisc"); - the_setup.GdCompatibility = true; - } break; - default: return false; - } - } - return true; +mgMuggle::mgMuggle (void) +{ + main = NULL; +// defaults for database arguments + the_setup.DbHost = strdup ("localhost"); + the_setup.DbSocket = NULL; + the_setup.DbPort = 0; + the_setup.DbName = strdup ("GiantDisc"); + the_setup.DbUser = strdup (""); + the_setup.DbPass = strdup (""); + the_setup.GdCompatibility = false; + the_setup.ToplevelDir = strdup ("/mnt/music/"); } -bool mgMuggle::Initialize(void) + +mgMuggle::~mgMuggle () { - // Initialize any background activities the plugin shall perform. - return true; +// Clean up after yourself! + if (main) main->SaveState(); } -bool mgMuggle::Start(void) + +const char * +mgMuggle::CommandLineHelp (void) { - // Start any background activities the plugin shall perform. - mgSetDebugLevel( 99 ); - RegisterI18n( Phrases ); +// Return a string that describes all known command line options. + return + " -h HHHH, --host=HHHH specify database host (default is localhost)\n" + " -s SSSS --socket=PATH specify database socket (default is TCP connection)\n" + " -n NNNN, --name=NNNN specify database name (overridden by -g)\n" + " -p PPPP, --port=PPPP specify port of database server (default is )\n" + " -u UUUU, --user=UUUU specify database user (default is )\n" + " -w WWWW, --password=WWWW specify database password (default is empty)\n" + " -t TTTT, --toplevel=TTTT specify toplevel directory for music (default is /mnt/music)\n" + " -g, --giantdisc enable full Giantdisc compatibility mode\n"; +} - // Database initialization - m_media = new mgMedia( mgMedia::GD_MP3 ); - m_root = m_media->getSelectionRoot(); - m_playlist = m_media->createTemporaryPlaylist(); - m_media->initFilterSet(); - // Read commands for playlists in etc. /video/muggle/playlist_commands.conf - m_playlist_commands = new cCommands(); +bool mgMuggle::ProcessArgs (int argc, char *argv[]) +{ + mgDebug (1, "mgMuggle::ProcessArgs"); - char *cmd_file = (char *) AddDirectory( cPlugin::ConfigDirectory("muggle"), "playlist_commands.conf" ); - mgDebug( 1, "mgMuggle::Start: Looking for file %s", cmd_file ); - bool have_cmd_file = m_playlist_commands->Load( (const char*) cmd_file ); +// Implement command line argument processing here if applicable. + static struct option + long_options[] = + { + {"host", required_argument, NULL, 'h'}, + {"socket", required_argument, NULL, 's'}, + {"name", required_argument, NULL, 'n'}, + {"port", required_argument, NULL, 'p'}, + {"user", required_argument, NULL, 'u'}, + {"password", required_argument, NULL, 'w'}, + {"toplevel", required_argument, NULL, 't'}, + {"giantdisc", no_argument, NULL, 'g'}, + {NULL} + }; - if( !have_cmd_file ) + int + c, + option_index = 0; + while ((c = + getopt_long (argc, argv, "gh:s:n:p:t:u:w:", long_options, + &option_index)) != -1) { - delete m_playlist_commands; - m_playlist_commands = NULL; + switch (c) + { + case 'h': + { + the_setup.DbHost = strcpyrealloc (the_setup.DbHost, optarg); + } + break; + case 's': + { + the_setup.DbSocket = strcpyrealloc (the_setup.DbSocket, optarg); + } + break; + case 'n': + { + the_setup.DbName = strcpyrealloc (the_setup.DbName, optarg); + } + break; + case 'p': + { + the_setup.DbPort = atoi (optarg); + } + break; + case 'u': + { + the_setup.DbUser = strcpyrealloc (the_setup.DbUser, optarg); + } + break; + case 'w': + { + the_setup.DbPass = strcpyrealloc (the_setup.DbPass, optarg); + } + break; + case 't': + { + if (optarg[strlen (optarg) - 1] != '/') + { + std::string res = std::string (optarg) + "/"; + the_setup.ToplevelDir = strdup (res.c_str ()); + } + else + { + the_setup.ToplevelDir = + strcpyrealloc (the_setup.ToplevelDir, optarg); + } + } + break; + case 'g': + { + the_setup.DbName = strcpyrealloc (the_setup.DbName, "GiantDisc"); + the_setup.GdCompatibility = true; + } + break; + default: + return false; + } } - return true; + return true; } -void mgMuggle::Housekeeping(void) + +bool mgMuggle::Initialize (void) { - // Perform any cleanup or other regular tasks. +// Initialize any background activities the plugin shall perform. + return true; } -cOsdObject *mgMuggle::MainMenuAction(void) -{ - // Perform the action when selected from the main VDR menu. - cOsdObject* osd = new mgMainMenu( m_media, m_root, m_playlist, - m_playlist_commands ); - return osd; +bool mgMuggle::Start (void) +{ +// Start any background activities the plugin shall perform. + mgSetDebugLevel (99); + RegisterI18n (Phrases); + return true; } -cMenuSetupPage *mgMuggle::SetupMenu(void) + +void +mgMuggle::Housekeeping (void) { - return new mgMenuSetup(); +// Perform any cleanup or other regular tasks. } -bool mgMuggle::SetupParse(const char *Name, const char *Value) + +cOsdObject * +mgMuggle::MainMenuAction (void) { - mgDebug( 1, "mgMuggle::SetupParse" ); - - if (!strcasecmp(Name, "InitLoopMode")) the_setup.InitLoopMode = atoi(Value); - else if (!strcasecmp(Name, "InitShuffleMode")) the_setup.InitShuffleMode = atoi(Value); - else if (!strcasecmp(Name, "AudioMode")) the_setup.AudioMode = atoi(Value); - else if (!strcasecmp(Name, "DisplayMode")) the_setup.DisplayMode = atoi(Value); - else if (!strcasecmp(Name, "BackgrMode")) the_setup.BackgrMode = atoi(Value); - else if (!strcasecmp(Name, "TargetLevel")) the_setup.TargetLevel = atoi(Value); - else if (!strcasecmp(Name, "LimiterLevel")) the_setup.LimiterLevel = atoi(Value); - else if (!strcasecmp(Name, "Only48kHz")) the_setup.Only48kHz = atoi(Value); - else return false; - - return true; +// Perform the action when selected from the main VDR menu. + main = new mgMainMenu (); + return main; } -void mgMuggle::setResumeIndex( unsigned index ) +cMenuSetupPage * +mgMuggle::SetupMenu (void) { - s_resume_idx = index; + return new mgMenuSetup (); } -unsigned mgMuggle::getResumeIndex( ) + +bool mgMuggle::SetupParse (const char *Name, const char *Value) { - return s_resume_idx; + if (!strcasecmp (Name, "InitLoopMode")) + the_setup.InitLoopMode = atoi (Value); + else if (!strcasecmp (Name, "InitShuffleMode")) + the_setup.InitShuffleMode = atoi (Value); + else if (!strcasecmp (Name, "AudioMode")) + the_setup.AudioMode = atoi (Value); + else if (!strcasecmp (Name, "DisplayMode")) + the_setup.DisplayMode = atoi (Value); + else if (!strcasecmp (Name, "BackgrMode")) + the_setup.BackgrMode = atoi (Value); + else if (!strcasecmp (Name, "TargetLevel")) + the_setup.TargetLevel = atoi (Value); + else if (!strcasecmp (Name, "LimiterLevel")) + the_setup.LimiterLevel = atoi (Value); + else if (!strcasecmp (Name, "Only48kHz")) + the_setup.Only48kHz = atoi (Value); + else + return false; + + return true; } -VDRPLUGINCREATOR(mgMuggle); // Don't touch this! + +VDRPLUGINCREATOR (mgMuggle); // Don't touch this! diff --git a/muggle.doxygen b/muggle.doxygen index fb0e1d0..4e601ad 100644 --- a/muggle.doxygen +++ b/muggle.doxygen @@ -23,7 +23,7 @@ PROJECT_NAME = Muggle media plugin # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = 0.0.8 +PROJECT_NUMBER = 0.0.9 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. @@ -1,4 +1,4 @@ -/*! +/*! * \file muggle.h * \ingroup vdr * \brief Implements a plugin for browsing media libraries within VDR @@ -26,7 +26,7 @@ * \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 + * means to organize */ #ifndef _MUGGLE_H @@ -34,53 +34,38 @@ #include <string> #include <plugin.h> -class mgMedia; -class mgSelectionTreeNode; -class mgPlaylist; +class mgMainMenu; -class cCommands; - -class mgMuggle : public cPlugin +class mgMuggle:public cPlugin { -public: - - mgMuggle(void); - - virtual ~mgMuggle(); + public: - virtual const char *Version(void); + mgMuggle (void); - virtual const char *Description(void); + virtual ~ mgMuggle (); - virtual const char *CommandLineHelp(void); + virtual const char *Version (void); - virtual bool ProcessArgs(int argc, char *argv[]); + virtual const char *Description (void); - virtual bool Initialize(void); + virtual const char *CommandLineHelp (void); - virtual bool Start(void); + virtual bool ProcessArgs (int argc, char *argv[]); - virtual void Housekeeping(void); + virtual bool Initialize (void); - virtual const char *MainMenuEntry(void); + virtual bool Start (void); - virtual cOsdObject *MainMenuAction(void); + virtual void Housekeeping (void); - virtual cMenuSetupPage *SetupMenu(void); + virtual const char *MainMenuEntry (void); - virtual bool SetupParse(const char *Name, const char *Value); + virtual cOsdObject *MainMenuAction (void); - static void setResumeIndex( unsigned index ); + virtual cMenuSetupPage *SetupMenu (void); - static unsigned getResumeIndex( ); - -private: - - mgMedia *m_media; - mgSelectionTreeNode *m_root; - mgPlaylist *m_playlist; - cCommands *m_playlist_commands; + virtual bool SetupParse (const char *Name, const char *Value); + mgMainMenu *main; }; - #endif @@ -5,7 +5,7 @@ * \author Lars von Wedel */ -#define VERBOSE +// #define VERBOSE #include <string> #include <stdlib.h> @@ -14,7 +14,13 @@ #include <sys/time.h> #include <mysql/mysql.h> #include <getopt.h> -#include <unistd.h> +/*extern "C" +{*/ + #include <stdarg.h> + #include <stdio.h> +/*} +*/ +#include <stdlib.h> #include <tag.h> #include <fileref.h> @@ -26,6 +32,40 @@ MYSQL *db; std::string host, user, pass, dbname, sck; bool import_assorted, delete_mode; +#define MAX_QUERY_BUFLEN 2048 +static char querybuf[MAX_QUERY_BUFLEN]; + +MYSQL_RES* mgSqlReadQuery(MYSQL *db, const char *fmt, ...) +{ + va_list ap; + va_start( ap, fmt ); + vsnprintf( querybuf, MAX_QUERY_BUFLEN-1, fmt, ap ); + + if( mysql_query(db, querybuf) ) + { + mgError( "SQL error in MUGGLE:\n%s\n", querybuf ); + } + + MYSQL_RES *result = mysql_store_result(db); + + va_end(ap); + return result; +} + +void mgSqlWriteQuery(MYSQL *db, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsnprintf(querybuf, MAX_QUERY_BUFLEN-1, fmt, ap); + + if( mysql_query(db, querybuf) ) + { + mgError( "SQL error in MUGGLE:\n%s\n", querybuf ); + } + + va_end(ap); +} + int init_database() { db = mysql_init(0); // NULL? @@ -280,13 +320,13 @@ void update_db( long uid, std::string filename ) #ifdef VERBOSE std::cout << "-- TAG --" << std::endl; - std::cout << "title - \"" << tag->title() << "\"" << std::endl; - std::cout << "artist - \"" << tag->artist() << "\"" << std::endl; - std::cout << "album - \"" << tag->album() << "\"" << std::endl; - std::cout << "year - \"" << tag->year() << "\"" << std::endl; - std::cout << "comment - \"" << tag->comment() << "\"" << std::endl; - std::cout << "track - \"" << tag->track() << "\"" << std::endl; - std::cout << "genre - \"" << tag->genre() << "\"" << std::endl; + std::cout << "title - '" << tag->title() << "'" << std::endl; + std::cout << "artist - '" << tag->artist() << "'" << std::endl; + std::cout << "album - '" << tag->album() << "'" << std::endl; + std::cout << "year - '" << tag->year() << "'" << std::endl; + std::cout << "comment - '" << tag->comment() << "'" << std::endl; + std::cout << "track - '" << tag->track() << "'" << std::endl; + std::cout << "genre - '" << tag->genre() << "'" << std::endl; #endif } } @@ -348,7 +388,7 @@ void evaluate_file( std::string filename ) long uid = find_file_in_database( db, filename ); if( uid >= 0 ) { - // currently only update database, do not consider writing changes from the db back + // currently only update database, do not consider writing changes from the db back to tags /* // determine modification times in database and on filesystem time_t db_time = get_db_modification_time( uid ); @@ -457,6 +497,11 @@ int main( int argc, char *argv[] ) } } + if( filename.length() > 255 ) + { + std::cerr << "Warning: length of file exceeds database field capacity: " << filename << std::endl; + } + // init random number generator struct timeval tv; struct timezone tz; diff --git a/scripts/createdb.mysql b/scripts/createdb.mysql index 68c12d7..7ccdc89 100755 --- a/scripts/createdb.mysql +++ b/scripts/createdb.mysql @@ -1,14 +1,14 @@ --- Creates DB and opens it to any user --- Run this mysql macro as root! +/* Creates DB and opens it to any user */ +/* Run this mysql macro as root! */ DROP DATABASE IF EXISTS GiantDisc; CREATE DATABASE GiantDisc; use GiantDisc; --- The first line is useful for granting access to user vdr on all computers in a network. --- grant all privileges on GiantDisc.* to vdr@'%'; +/* The first line is useful for granting access to user vdr on all computers in a network. */ +/* grant all privileges on GiantDisc.* to vdr@'%'; */ --- Grant access to user vdr on the local machine +/* Grant access to user vdr on the local machine */ grant all privileges on GiantDisc.* to vdr@localhost; diff --git a/scripts/createtables.mysql b/scripts/createtables.mysql index 402f66b..079b3bd 100755 --- a/scripts/createtables.mysql +++ b/scripts/createtables.mysql @@ -8,7 +8,7 @@ -- Current Database: GiantDisc -- --- CREATE DATABASE /*!32312 IF NOT EXISTS*/ GiantDisc; +--CREATE DATABASE /*!32312 IF NOT EXISTS*/ GiantDisc; USE GiantDisc; diff --git a/vdr_config.h b/vdr_config.h index 6178a34..20f7d23 100644 --- a/vdr_config.h +++ b/vdr_config.h @@ -1,13 +1,13 @@ -/*! +/*! * \file vdr_menu.c * \brief Implements menu handling for browsing media libraries within 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_config.h,v 1.2 2004/05/28 15:29:18 lvw Exp $ + * $Id$ * * Adapted from * MP3/MPlayer plugin to VDR (C++) @@ -115,5 +115,4 @@ // "/tmp/limiter". The generated file will be about 3MB in size. This option shouldn't // be enabled for day-by-day operation. //#define ACC_DUMP - -#endif //___CONFIG_H +#endif //___CONFIG_H diff --git a/vdr_decoder.c b/vdr_decoder.c index 0384031..9b47fa8 100644 --- a/vdr_decoder.c +++ b/vdr_decoder.c @@ -25,128 +25,151 @@ #include "vdr_decoder_mp3.h" #include "vdr_decoder_ogg.h" -#include "mg_content_interface.h" +#include "mg_db.h" #include <videodir.h> #include <interface.h> // --- mgDecoders --------------------------------------------------------------- -mgMediaType mgDecoders::getMediaType( std::string s ) +mgMediaType mgDecoders::getMediaType (std::string s) { - mgMediaType mt = MT_UNKNOWN; + mgMediaType + mt = MT_UNKNOWN; - // TODO: currently handles only mp3. LVW - char *f = (char *)s.c_str(); - char *p = f + strlen( f ) - 1; // point to the end +// TODO: currently handles only mp3. LVW + char * + f = (char *) s.c_str (); + char * + p = f + strlen (f) - 1; // point to the end - while( p >= f && *p != '.') --p; + while (p >= f && *p != '.') + --p; - if( !strcmp( p, ".mp3" ) ) + if (!strcmp (p, ".mp3")) { - mt = MT_MP3; + mt = MT_MP3; } - else + else { - if( !strcmp( p, ".ogg" ) ) - { - mt = MT_OGG; - } + if (!strcmp (p, ".ogg")) + { + mt = MT_OGG; + } } - return mt; + return mt; } -mgDecoder *mgDecoders::findDecoder( mgContentItem *item ) + +mgDecoder * +mgDecoders::findDecoder (mgContentItem * item) { - mgDecoder *decoder = 0; + mgDecoder *decoder = 0; - std::string filename = item->getSourceFile(); + std::string filename = item->getSourceFile (); - switch( getMediaType( filename ) ) + struct stat st; + if (stat (filename.c_str (), &st)) + { + esyslog ("ERROR: no valid decoder found for %s", filename.c_str ()); + return 0; + } + switch (getMediaType (filename)) { - case MT_MP3: - { - decoder = new mgMP3Decoder( item ); - } break; + case MT_MP3: + { + decoder = new mgMP3Decoder (item); + } + break; #ifdef HAVE_VORBISFILE - case MT_OGG: - { - decoder = new mgOggDecoder( item ); - } break; + 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; +/* + 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() ) + + if (decoder && !decoder->valid ()) { - // no decoder found or decoder doesn't match - - delete decoder; // might be carried out on NULL pointer! - decoder = 0; - - esyslog("ERROR: no valid decoder found for %s", filename.c_str() ); +// no decoder found or decoder doesn't match + + delete decoder; // might be carried out on NULL pointer! + decoder = 0; + + esyslog ("ERROR: no valid decoder found for %s", filename.c_str ()); } - return decoder; + return decoder; } + // --- mgDecoder ---------------------------------------------------------------- -mgDecoder::mgDecoder( mgContentItem *item ) +mgDecoder::mgDecoder (mgContentItem * item) { - m_item = item; - m_locked = 0; - m_urgentLock = false; - m_playing = false; + m_item = item; + m_locked = 0; + m_urgentLock = false; + m_playing = false; } -mgDecoder::~mgDecoder() + +mgDecoder::~mgDecoder () { } -void mgDecoder::lock(bool urgent) + +void +mgDecoder::lock (bool urgent) { - m_locklock.Lock(); + m_locklock.Lock (); - if( urgent && m_locked ) + if (urgent && m_locked) { - m_urgentLock = true; // signal other locks to release quickly + m_urgentLock = true; // signal other locks to release quickly } - m_locked ++; + m_locked++; - m_locklock.Unlock(); // don't hold the "locklock" when locking "lock", may cause a deadlock - m_lock.Lock(); - m_urgentLock = false; + m_locklock.Unlock (); // don't hold the "locklock" when locking "lock", may cause a deadlock + m_lock.Lock (); + m_urgentLock = false; } -void mgDecoder::unlock(void) + +void +mgDecoder::unlock (void) { - m_locklock.Lock(); + m_locklock.Lock (); - m_locked--; + m_locked--; - m_lock.Unlock(); - m_locklock.Unlock(); + m_lock.Unlock (); + m_locklock.Unlock (); } -bool mgDecoder::tryLock(void) + +bool mgDecoder::tryLock (void) { - bool res = false; - m_locklock.Lock(); + bool + res = false; + m_locklock.Lock (); - if( !m_locked && !m_playing ) + if (!m_locked && !m_playing) { - lock(); - res = true; + lock (); + res = true; } - m_locklock.Unlock(); - return res; + m_locklock.Unlock (); + return res; } diff --git a/vdr_decoder.h b/vdr_decoder.h index a673cbc..ee943ce 100644 --- a/vdr_decoder.h +++ b/vdr_decoder.h @@ -10,7 +10,7 @@ * * $Id$ * - * Adapted from + * Adapted from * MP3/MPlayer plugin to VDR (C++) * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> */ @@ -29,143 +29,142 @@ class mgContentItem; // --------From decoder_core.h ------------------------------------ -/*! +/*! * \brief The current status of the decoder * \ingroup vdr */ enum eDecodeStatus - { - dsOK=0, dsPlay, dsSkip, dsEof, dsError, dsSoftError - }; +{ + dsOK = 0, dsPlay, dsSkip, dsEof, dsError, dsSoftError +}; // ---------------------------------------------------------------- -/*! +/*! * \brief A data structure to put decoded PCM data * \ingroup vdr */ struct mgDecode { - eDecodeStatus status; - int index; - struct mad_pcm *pcm; + eDecodeStatus status; + int index; + struct mad_pcm *pcm; }; // ---------------------------------------------------------------- -/*! +/*! * \brief Information about ??? * \ingroup vdr */ class mgPlayInfo { -public: - int m_index, m_total; + public: + int m_index, m_total; }; // ---------------------------------------------------------------- -/*! +/*! * \brief Media types * \ingroup vdr */ enum mgMediaType { - MT_MP3, MT_MP3_STREAM, MT_OGG, MT_FLAC, MT_UNKNOWN + 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: + protected: - /*! \brief database handle to the track being decoded */ - mgContentItem *m_item; +/*! \brief database handle to the track being decoded */ + mgContentItem * m_item; - /*! \brief The currently playing file */ - std::string m_filename; +/*! \brief The currently playing file */ + std::string m_filename; - /*! \brief Mutexes to coordinate threads */ - cMutex m_lock, m_locklock; - int m_locked; - bool m_urgentLock; - - /*! \brief Whether the decoder is currently active */ - bool m_playing; +/*! \brief Mutexes to coordinate threads */ + cMutex m_lock, m_locklock; + int m_locked; + bool m_urgentLock; - /*! \brief ??? */ - mgPlayInfo m_playinfo; +/*! \brief Whether the decoder is currently active */ + bool m_playing; - /*! \brief Place a lock */ - virtual void lock( bool urgent = false ); +/*! \brief ??? */ + mgPlayInfo m_playinfo; - /*! \brief Release a lock */ - virtual void unlock(void); +/*! \brief Place a lock */ + virtual void lock (bool urgent = false); - /*! \brief Try to obtain a lock */ - virtual bool tryLock(void); +/*! \brief Release a lock */ + virtual void unlock (void); -public: +/*! \brief Try to obtain a lock */ + virtual bool tryLock (void); - //@{ - /*! \brief The constructor */ - mgDecoder( mgContentItem *item ); + public: - /*! \brief The destructor */ - virtual ~mgDecoder(); - //@} +//@{ +/*! \brief The constructor */ + mgDecoder (mgContentItem * item); - /*! \brief Whether a decoder instance is able to play the given file */ - virtual bool valid() = 0; +/*! \brief The destructor */ + virtual ~ mgDecoder (); +//@} - /*! \brief Whether a stream (i.e. from the network is being decoded */ - virtual bool isStream() - { - return false; - } +/*! \brief Whether a decoder instance is able to play the given file */ + virtual bool valid () = 0; - /*! \brief Start decoding */ - virtual bool start() = 0; +/*! \brief Whether a stream (i.e. from the network is being decoded */ + virtual bool isStream () + { + return false; + } - /*! \brief Stop decoding */ - virtual bool stop() = 0; +/*! \brief Start decoding */ + virtual bool start () = 0; - /*! \brief Skip an amount of time. Impossible by default */ - virtual bool skip( int seconds, int avail, int rate) - { - return false; - } +/*! \brief Stop decoding */ + virtual bool stop () = 0; - /*! \brief Return decoded data */ - virtual struct mgDecode *decode() = 0; +/*! \brief Skip an amount of time. Impossible by default */ + virtual bool skip (int seconds, int avail, int rate) + { + return false; + } - /*! \brief Information about the current playback status */ - virtual mgPlayInfo *playInfo() - { - return 0; - } +/*! \brief Return decoded data */ + virtual struct mgDecode *decode () = 0; + +/*! \brief Information about the current playback status */ + virtual mgPlayInfo *playInfo () + { + return 0; + } }; - + // ---------------------------------------------------------------- -/*! +/*! * \brief A generic decoder manager class to handle different decoders */ class mgDecoders { -public: - - /*! \brief Try to find a valid decoder for a file - */ - static mgDecoder *findDecoder( mgContentItem *item ); + public: - /*! \brief determine the media type for a given source - */ - static mgMediaType getMediaType( std::string filename ); +/*! \brief Try to find a valid decoder for a file + */ + static mgDecoder *findDecoder (mgContentItem * item); -}; +/*! \brief determine the media type for a given source + */ + static mgMediaType getMediaType (std::string filename); -#endif //___DECODER_H +}; +#endif //___DECODER_H diff --git a/vdr_decoder_mp3.c b/vdr_decoder_mp3.c index 6f2d7f6..d914dcd 100644 --- a/vdr_decoder_mp3.c +++ b/vdr_decoder_mp3.c @@ -9,7 +9,7 @@ * * $Id$ * - * Adapted from + * Adapted from * MP3/MPlayer plugin to VDR (C++) * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> */ @@ -27,393 +27,435 @@ #include "vdr_decoder_mp3.h" #include "vdr_stream.h" -#include "mg_content_interface.h" #include "mg_tools.h" +#include "mg_db.h" #define d(x) x - // ---------------------------------------------------------------- -int mgMadStream(struct mad_stream *stream, mgStream *str) +int +mgMadStream (struct mad_stream *stream, mgStream * str) { - unsigned char *data; - unsigned long len; - if( str->stream( data, len, stream->next_frame ) ) + unsigned char *data; + unsigned long len; + if (str->stream (data, len, stream->next_frame)) { - if( len > 0 ) - { - mad_stream_buffer(stream, data, len); - } - return len; + if (len > 0) + { + mad_stream_buffer (stream, data, len); + } + return len; } - return -1; + return -1; } + // --- mgMP3Decoder ------------------------------------------------------------- -mgMP3Decoder::mgMP3Decoder( mgContentItem *item, bool preinit) - : mgDecoder(item) +mgMP3Decoder::mgMP3Decoder (mgContentItem * item, bool preinit):mgDecoder +(item) { - m_stream = 0; - m_isStream = false; - m_filename = item->getSourceFile(); + m_stream = 0; + m_isStream = false; + m_filename = item->getSourceFile (); - if( preinit ) + if (preinit) { - m_stream = new mgStream( m_filename ); + m_stream = new mgStream (m_filename); + printf ("m_stream for %s\n", m_filename.c_str ()); + } - m_madframe = 0; - m_madsynth = 0; + m_madframe = 0; + m_madsynth = 0; - memset( &m_madstream, 0, sizeof(m_madstream) ); + memset (&m_madstream, 0, sizeof (m_madstream)); - init(); + init (); } -mgMP3Decoder::~mgMP3Decoder() + +mgMP3Decoder::~mgMP3Decoder () { - clean(); - delete m_stream; + clean (); + delete m_stream; } -void mgMP3Decoder::init() + +void +mgMP3Decoder::init () { - clean(); - mad_stream_init( &m_madstream ); + clean (); + mad_stream_init (&m_madstream); - m_madframe = new mad_frame; - mad_frame_init( m_madframe ); + m_madframe = new mad_frame; + mad_frame_init (m_madframe); - m_madsynth = new mad_synth; - mad_synth_init( m_madsynth ); + m_madsynth = new mad_synth; + mad_synth_init (m_madsynth); - mad_stream_options( &m_madstream, MAD_OPTION_IGNORECRC); + mad_stream_options (&m_madstream, MAD_OPTION_IGNORECRC); - m_playtime = mad_timer_zero; - m_skiptime = mad_timer_zero; - m_framenum = m_framemax = 0; - m_mute = m_errcount = 0; + m_playtime = mad_timer_zero; + m_skiptime = mad_timer_zero; + m_framenum = m_framemax = 0; + m_mute = m_errcount = 0; } -void mgMP3Decoder::clean() + +void +mgMP3Decoder::clean () { - m_playing = false; - if( m_madsynth ) + m_playing = false; + if (m_madsynth) { - mad_synth_finish( m_madsynth ); - delete m_madsynth; - m_madsynth = 0; + mad_synth_finish (m_madsynth); + delete m_madsynth; + m_madsynth = 0; } - if( m_madframe ) - { - mad_frame_finish( m_madframe ); - delete m_madframe; - m_madframe = 0; + if (m_madframe) + { + mad_frame_finish (m_madframe); + delete m_madframe; + m_madframe = 0; } - mad_stream_finish( &m_madstream ); + mad_stream_finish (&m_madstream); } -bool mgMP3Decoder::valid(void) + +bool mgMP3Decoder::valid (void) { - bool res = false; - if( tryLock() ) + bool + res = false; + if (tryLock ()) { - if( start() ) - { - struct mgDecode *dd; - int count = 10; - do - { - dd = decode(); - if( dd->status == dsEof ) - { - count=0; - } - if( dd->status != dsPlay) - { - break; - } - } while( --count ); - if( !count ) - { - res=true; - } - stop(); - } - unlock(); + if (start ()) + { + struct mgDecode * + dd; + int + count = 10; + do + { + dd = decode (); + if (dd->status == dsEof) + { + count = 0; + } + if (dd->status != dsPlay) + { + break; + } + } + while (--count); + if (!count) + { + res = true; + } + stop (); + } + unlock (); } - return res; + return res; } -mgPlayInfo *mgMP3Decoder::playInfo() + +mgPlayInfo * +mgMP3Decoder::playInfo () { - if( m_playing ) + if (m_playing) { - m_playinfo.m_index = mad_timer_count( m_playtime, MAD_UNITS_SECONDS ); + m_playinfo.m_index = mad_timer_count (m_playtime, MAD_UNITS_SECONDS); - return &m_playinfo; + return &m_playinfo; } - return 0; + return 0; } -bool mgMP3Decoder::start() + +bool mgMP3Decoder::start () { - lock(true); - init(); - m_playing = true; + lock (true); + init (); + m_playing = true; - if( m_stream->open(true) ) + if (m_stream->open (true)) { - if(!m_isStream) - { - m_stream->seek(); - - printf( "mgMP3Decoder::start: m_framemax not determined, rewinding disabled!!!\n" ); - - /* m_framemax = scan->Frames+20; // TODO - - m_frameinfo = new struct FrameInfo[m_framemax]; - if(!m_frameinfo) - { - printf( "mgMP3Decoder::start: no memory for frame index, rewinding disabled" ); - } - */ - } - unlock(); - - printf( "mgMP3Decoder::start: true\n" ); - return true; + if (!m_isStream) + { + m_stream->seek (); + + printf + ("mgMP3Decoder::start: m_framemax not determined, rewinding disabled!!!\n"); + +/* m_framemax = scan->Frames+20; // TODO + + m_frameinfo = new struct FrameInfo[m_framemax]; + if(!m_frameinfo) + { + printf( "mgMP3Decoder::start: no memory for frame index, rewinding disabled" ); + } + */ + } + unlock (); + + printf ("mgMP3Decoder::start: true\n"); + return true; } - m_stream->close(); - clean(); - unlock(); - - printf( "mgMP3Decoder::start: false" ); - return false; + m_stream->close (); + clean (); + unlock (); + printf ("mgMP3Decoder::start: false"); + return false; } -bool mgMP3Decoder::stop(void) + +bool mgMP3Decoder::stop (void) { - lock(); + lock (); - if( m_playing ) + if (m_playing) { - m_stream->close(); - clean(); + m_stream->close (); + clean (); } - unlock(); - return true; + unlock (); + return true; } -struct mgDecode *mgMP3Decoder::done(eDecodeStatus status) + +struct mgDecode * +mgMP3Decoder::done (eDecodeStatus status) { - m_ds.status = status; - m_ds.index = mad_timer_count( m_playtime, MAD_UNITS_MILLISECONDS ); - m_ds.pcm = &m_madsynth->pcm; - unlock(); // release the lock from Decode() + m_ds.status = status; + m_ds.index = mad_timer_count (m_playtime, MAD_UNITS_MILLISECONDS); + m_ds.pcm = &m_madsynth->pcm; + unlock (); // release the lock from Decode() - return &m_ds; + return &m_ds; } -eDecodeStatus mgMP3Decoder::decodeError(bool hdr) + +eDecodeStatus mgMP3Decoder::decodeError (bool hdr) { - if( m_madstream.error == MAD_ERROR_BUFLEN || m_madstream.error == MAD_ERROR_BUFPTR ) + if (m_madstream.error == MAD_ERROR_BUFLEN + || m_madstream.error == MAD_ERROR_BUFPTR) { - int s = mgMadStream( &m_madstream, m_stream ); - if( s < 0 ) - { - printf( "mgMP3Decoder::decodeError: dsError returned\n" ); - return dsError; - } - if( s == 0 ) - { - printf( "mgMP3Decoder::decodeError: dsEof returned\n" ); - return dsEof; - } + int + s = mgMadStream (&m_madstream, m_stream); + if (s < 0) + { + printf ("mgMP3Decoder::decodeError: dsError returned\n"); + return dsError; + } + if (s == 0) + { + printf ("mgMP3Decoder::decodeError: dsEof returned\n"); + return dsEof; + } } - else if( !MAD_RECOVERABLE( m_madstream.error ) ) + else if (!MAD_RECOVERABLE (m_madstream.error)) { - printf( "mgMP3Decoder::decodeError: mad decode %sfailed, frame=%d: %s. Returning dsError\n", - hdr? "hdr " : "", m_framenum, - mad_stream_errorstr( &m_madstream ) ); - return dsError; + printf + ("mgMP3Decoder::decodeError: mad decode %sfailed, frame=%d: %s. Returning dsError\n", + hdr ? "hdr " : "", m_framenum, mad_stream_errorstr (&m_madstream)); + return dsError; } - else - { - m_errcount += hdr? 1: 100; - printf( "mgMP3Decoder::decodeError: mad decode %s error, frame=%d count=%d: %s. Returning dsOK\n", - hdr? "hdr ": "", m_framenum, m_errcount, - mad_stream_errorstr( &m_madstream ) ); + else + { + m_errcount += hdr ? 1 : 100; + printf + ("mgMP3Decoder::decodeError: mad decode %s error, frame=%d count=%d: %s. Returning dsOK\n", + hdr ? "hdr " : "", m_framenum, m_errcount, + mad_stream_errorstr (&m_madstream)); } - return dsOK; + return dsOK; } -struct mgDecode *mgMP3Decoder::decode() + +struct mgDecode * +mgMP3Decoder::decode () { - lock(); // this is released in Done() - eDecodeStatus r; + lock (); // this is released in Done() + eDecodeStatus r; - while( m_playing ) + while (m_playing) { - if( m_errcount >= MAX_FRAME_ERR*100 ) - { - printf( "mgMP3Decoder::decode: excessive decoding errors," - " aborting file %s\n", m_filename.c_str() ); - return done(dsError); - } - - if( mad_header_decode( &m_madframe->header, &m_madstream) == -1) - { - if( (r = decodeError(true) ) ) - { - return done(r); - } - } - else - { - if(!m_isStream) - { + if (m_errcount >= MAX_FRAME_ERR * 100) + { + printf ("mgMP3Decoder::decode: excessive decoding errors," + " aborting file %s\n", m_filename.c_str ()); + return done (dsError); + } + + if (mad_header_decode (&m_madframe->header, &m_madstream) == -1) + { + if ((r = decodeError (true))) + { + return done (r); + } + } + else + { + if (!m_isStream) + { #ifdef DEBUG2 - if( m_framenum >= m_framemax ) - { - printf( "mgMP3Decoder::start: framenum >= framemax!!!!\n" ); - } + if (m_framenum >= m_framemax) + { + printf ("mgMP3Decoder::start: framenum >= framemax!!!!\n"); + } #endif - if( m_frameinfo && m_framenum < m_framemax ) - { - m_frameinfo[m_framenum].Pos = - m_stream->bufferPos() + - ( m_madstream.this_frame - m_madstream.buffer ); - m_frameinfo[m_framenum].Time = m_playtime; - } - } - - mad_timer_add( &m_playtime, m_madframe->header.duration); - m_framenum ++; - - if( mad_timer_compare(m_playtime, m_skiptime) >= 0 ) - { - m_skiptime = mad_timer_zero; - } - else - { - return done(dsSkip); // skipping, decode next header - } - - if( mad_frame_decode( m_madframe, &m_madstream ) == -1 ) - { - if( ( r = decodeError(false) ) ) - { - return done(r); - } - } - else - { - m_errcount = 0; - - // TODO: // m_scan->InfoHook( &frame->header ); - - mad_synth_frame( m_madsynth, m_madframe); - - if( m_mute ) - { - m_mute--; - return done( dsSkip ); - } - return done( dsPlay ); - } - } + if (m_frameinfo && m_framenum < m_framemax) + { + m_frameinfo[m_framenum].Pos = + m_stream->bufferPos () + + (m_madstream.this_frame - m_madstream.buffer); + m_frameinfo[m_framenum].Time = m_playtime; + } + } + + mad_timer_add (&m_playtime, m_madframe->header.duration); + m_framenum++; + + if (mad_timer_compare (m_playtime, m_skiptime) >= 0) + { + m_skiptime = mad_timer_zero; + } + else + { + return done (dsSkip); // skipping, decode next header + } + + if (mad_frame_decode (m_madframe, &m_madstream) == -1) + { + if ((r = decodeError (false))) + { + return done (r); + } + } + else + { + m_errcount = 0; + +// TODO: // m_scan->InfoHook( &frame->header ); + + mad_synth_frame (m_madsynth, m_madframe); + + if (m_mute) + { + m_mute--; + return done (dsSkip); + } + return done (dsPlay); + } + } } - return done( dsError ); + return done (dsError); } -void mgMP3Decoder::makeSkipTime( mad_timer_t *skiptime, - mad_timer_t playtime, - int secs, int avail, int dvbrate) + +void +mgMP3Decoder::makeSkipTime (mad_timer_t * skiptime, +mad_timer_t playtime, +int secs, int avail, int dvbrate) { - mad_timer_t time; + mad_timer_t time; - *skiptime = playtime; + *skiptime = playtime; - mad_timer_set( &time, abs(secs), 0, 0 ); + mad_timer_set (&time, abs (secs), 0, 0); - if( secs < 0 ) + if (secs < 0) { - mad_timer_negate(&time); + mad_timer_negate (&time); } - mad_timer_add( skiptime, time ); + mad_timer_add (skiptime, time); - float bufsecs = (float)avail / (float)(dvbrate * (16/8 * 2)); // Byte/s = samplerate * 16 bit * 2 chan + // Byte/s = samplerate * 16 bit * 2 chan + float bufsecs = (float) avail / (float) (dvbrate * (16 / 8 * 2)); - printf( "mgMP3Decoder::makeSkipTime: skip: avail=%d bufsecs=%f\n", avail, bufsecs ); + printf ("mgMP3Decoder::makeSkipTime: skip: avail=%d bufsecs=%f\n", avail, + bufsecs); - int full = (int)bufsecs; - bufsecs -= (float)full; + int full = (int) bufsecs; + bufsecs -= (float) full; - mad_timer_set( &time, full, (int)(bufsecs*1000.0), 1000); + mad_timer_set (&time, full, (int) (bufsecs * 1000.0), 1000); - mad_timer_negate(&time); + mad_timer_negate (&time); - mad_timer_add( skiptime, time ); + mad_timer_add (skiptime, time); - printf( "mgMP3Decoder::makeSkipTime: skip: playtime=%ld secs=%d full=%d bufsecs=%f skiptime=%ld\n", - mad_timer_count(playtime,MAD_UNITS_MILLISECONDS), - secs, full, bufsecs, - mad_timer_count(*skiptime,MAD_UNITS_MILLISECONDS ) ); + printf + ("mgMP3Decoder::makeSkipTime: skip: playtime=%ld secs=%d full=%d bufsecs=%f skiptime=%ld\n", + mad_timer_count (playtime, MAD_UNITS_MILLISECONDS), secs, full, bufsecs, + mad_timer_count (*skiptime, MAD_UNITS_MILLISECONDS)); } -bool mgMP3Decoder::skip( int seconds, int avail, int rate ) + +bool mgMP3Decoder::skip (int seconds, int avail, int rate) { - lock(); + lock (); - bool res = false; - if( m_playing && !m_isStream ) + bool + res = false; + if (m_playing && !m_isStream) { - if( !mad_timer_compare( m_skiptime, mad_timer_zero ) ) - { // allow only one skip at any time - mad_timer_t time; - makeSkipTime( &time, m_playtime, seconds, avail, rate); - - if( mad_timer_compare( m_playtime, time ) <= 0 ) - { // forward skip + if (!mad_timer_compare (m_skiptime, mad_timer_zero)) + { // allow only one skip at any time + mad_timer_t + time; + makeSkipTime (&time, m_playtime, seconds, avail, rate); + + if (mad_timer_compare (m_playtime, time) <= 0) + { // forward skip #ifdef DEBUG - int i = mad_timer_count( time, MAD_UNITS_SECONDS ); - printf( "mgMP3Decoder::skip: forward skipping to %02d:%02d\n", i/60, i%60 ); + int + i = mad_timer_count (time, MAD_UNITS_SECONDS); + printf ("mgMP3Decoder::skip: forward skipping to %02d:%02d\n", + i / 60, i % 60); #endif - m_skiptime = time; - m_mute=1; - res = true; - } - else - { // backward skip - if( m_frameinfo ) - { + m_skiptime = time; + m_mute = 1; + res = true; + } + else + { // backward skip + if (m_frameinfo) + { #ifdef DEBUG - int i = mad_timer_count( time, MAD_UNITS_SECONDS ); - printf( "mgMP3Decoder::skip: rewinding to %02d:%02d\n", i/60, i%60 ); + int + i = mad_timer_count (time, MAD_UNITS_SECONDS); + printf ("mgMP3Decoder::skip: rewinding to %02d:%02d\n", + i / 60, i % 60); #endif - while( m_framenum && mad_timer_compare( time, m_frameinfo[--m_framenum].Time) < 0) ; - m_mute = 2; - if( m_framenum >= 2) - { - m_framenum-=2; - } - m_playtime = m_frameinfo[m_framenum].Time; - m_stream->seek( m_frameinfo[m_framenum].Pos ); - mad_stream_finish( &m_madstream ); // reset stream buffer - mad_stream_init( &m_madstream ); + while (m_framenum + && mad_timer_compare (time, + m_frameinfo[--m_framenum]. + Time) < 0); + m_mute = 2; + if (m_framenum >= 2) + { + m_framenum -= 2; + } + m_playtime = m_frameinfo[m_framenum].Time; + m_stream->seek (m_frameinfo[m_framenum].Pos); + // reset stream buffer + mad_stream_finish (&m_madstream); + mad_stream_init (&m_madstream); #ifdef DEBUG - i = mad_timer_count( m_playtime, MAD_UNITS_MILLISECONDS ); - printf( "mgMP3Decoder::skip: new playtime=%d framenum=%d filepos=%lld\n", i, m_framenum, m_frameinfo[m_framenum].Pos); + i = mad_timer_count (m_playtime, MAD_UNITS_MILLISECONDS); + printf + ("mgMP3Decoder::skip: new playtime=%d framenum=%d filepos=%lld\n", + i, m_framenum, m_frameinfo[m_framenum].Pos); #endif - res = true; - } - } - } + res = true; + } + } + } } - unlock(); - return res; + unlock (); + return res; } diff --git a/vdr_decoder_mp3.h b/vdr_decoder_mp3.h index f9f3bde..47d6eb2 100644 --- a/vdr_decoder_mp3.h +++ b/vdr_decoder_mp3.h @@ -9,7 +9,7 @@ * * $Id$ * - * Adapted from + * Adapted from * MP3/MPlayer plugin to VDR (C++) * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> */ @@ -37,79 +37,78 @@ class mgContentItem; /*! * \brief A class to decode mp3 songs into PCM using libmad */ -class mgMP3Decoder : public mgDecoder +class mgMP3Decoder:public mgDecoder { -private: - struct mgDecode m_ds; + private: + struct mgDecode m_ds; - // - struct mad_stream m_madstream; - struct mad_frame *m_madframe; - struct mad_synth *m_madsynth; - mad_timer_t m_playtime, m_skiptime; +// + struct mad_stream m_madstream; + struct mad_frame *m_madframe; + struct mad_synth *m_madsynth; + mad_timer_t m_playtime, m_skiptime; - // - struct FrameInfo - { - unsigned long long Pos; - mad_timer_t Time; - } *m_frameinfo; +// + struct FrameInfo + { + unsigned long long Pos; + mad_timer_t Time; + } *m_frameinfo; - int m_framenum, m_framemax, m_errcount, m_mute; + int m_framenum, m_framemax, m_errcount, m_mute; - void init(); + void init (); - void clean(); + void clean (); - struct mgDecode *done( eDecodeStatus status ); + struct mgDecode *done (eDecodeStatus status); - virtual mgPlayInfo *playInfo(); + virtual mgPlayInfo *playInfo (); - eDecodeStatus decodeError(bool hdr); - - void makeSkipTime(mad_timer_t *skiptime, mad_timer_t playtime, - int secs, int avail, int dvbrate); + eDecodeStatus decodeError (bool hdr); -protected: - mgStream *m_stream; - bool m_isStream; + void makeSkipTime (mad_timer_t * skiptime, mad_timer_t playtime, + int secs, int avail, int dvbrate); -public: + protected: + mgStream * m_stream; + bool m_isStream; - /*! - * \brief construct a decoder from a filename - */ - mgMP3Decoder( mgContentItem *item, bool preinit = true ); + public: - /*! - * \brief the destructor - */ - virtual ~mgMP3Decoder(); +/*! + * \brief construct a decoder from a filename + */ + mgMP3Decoder (mgContentItem * item, bool preinit = true); + +/*! + * \brief the destructor + */ + virtual ~ mgMP3Decoder (); - /*! - * \brief check, whether the file contains useable MP3 content - */ - virtual bool valid(); +/*! + * \brief check, whether the file contains useable MP3 content + */ + virtual bool valid (); - /*! - * \brief start the decoding process - */ - virtual bool start(); +/*! + * \brief start the decoding process + */ + virtual bool start (); - /*! - * \brief stop the decoding process - */ - virtual bool stop(); +/*! + * \brief stop the decoding process + */ + virtual bool stop (); - /*! - * \brief skip an amount of seconds - */ - virtual bool skip( int seconds, int avail, int rate ); +/*! + * \brief skip an amount of seconds + */ + virtual bool skip (int seconds, int avail, int rate); - /*! - * \brief the actual decoding function (uses libmad) - */ - virtual struct mgDecode *decode(); +/*! + * \brief the actual decoding function (uses libmad) + */ + virtual struct mgDecode *decode (); }; - -#endif //___DECODER_MP3_H +#endif //___DECODER_MP3_H diff --git a/vdr_decoder_ogg.c b/vdr_decoder_ogg.c index 94bd37d..d720c81 100644 --- a/vdr_decoder_ogg.c +++ b/vdr_decoder_ogg.c @@ -1,14 +1,13 @@ /*! \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 + * Adapted from * MP3/MPlayer plugin to VDR (C++) * (C) 2001-2003 Stefan Huelswitt <huels@iname.com> */ - #ifdef HAVE_VORBISFILE #include "vdr_decoder_ogg.h" @@ -20,373 +19,441 @@ #include <stdlib.h> #include <stdio.h> -#include "mg_content_interface.h" - +#include "mg_db.h" // --- mgOggFile ---------------------------------------------------------------- -class mgOggFile // : public mgFileInfo +class mgOggFile // : public mgFileInfo { - private: + private: + + bool m_opened, m_canSeek; - bool m_opened, m_canSeek; + OggVorbis_File vf; - OggVorbis_File vf; + void error (const char *action, const int err); - void error( const char *action, const int err ); + std::string m_filename; - std::string m_filename; + public: - public: + mgOggFile (std::string filename); + ~mgOggFile (); - mgOggFile( std::string filename ); - ~mgOggFile(); + bool open (bool log = true); - bool open(bool log=true); + void close (void); - void close(void); + long long seek (long long posMs = 0, bool relativ = false); - long long seek(long long posMs=0, bool relativ=false); + int stream (short *buffer, int samples); - int stream(short *buffer, int samples); - - bool canSeek() { return m_canSeek; } + bool canSeek () + { + return m_canSeek; + } - long long indexMs(void); + long long indexMs (void); }; -mgOggFile::mgOggFile( std::string filename ) : - m_filename( filename ) +mgOggFile::mgOggFile (std::string filename): +m_filename (filename) { - m_canSeek = false; - m_opened = false; + m_canSeek = false; + m_opened = false; } -mgOggFile::~mgOggFile() + +mgOggFile::~mgOggFile () { - close(); + close (); } -bool mgOggFile::open(bool log) + +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 ) + if (m_opened) { - 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 ); - } - } + if (m_canSeek) + { + return (seek () >= 0); + } + return true; } - else + + 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) ); - } + if (log) + { +// esyslog("ERROR: failed to open file %s: %s", m_filename.c_str(), strerror(errno) ); + } } - return m_opened; + return m_opened; } - -void mgOggFile::close() + + +void +mgOggFile::close () { - if( m_opened ) - { - ov_clear( &vf ); - m_opened = false; + if (m_opened) + { + ov_clear (&vf); + m_opened = false; } } -void mgOggFile::error( const char *action, const int err ) + +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 ); + 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) + +long long +mgOggFile::indexMs (void) { - double p = ov_time_tell(&vf); - if( p < 0.0 ) + double p = ov_time_tell (&vf); + if (p < 0.0) { - p = 0.0; + p = 0.0; } - return (long long)( p*1000.0 ); + return (long long) (p * 1000.0); } -long long mgOggFile::seek( long long posMs, bool relativ ) + +long long +mgOggFile::seek (long long posMs, bool relativ) { - if( relativ ) + if (relativ) { - posMs += indexMs(); + posMs += indexMs (); } - int r = ov_time_seek( &vf, (double) posMs/1000.0 ); - - if(r) + int r = ov_time_seek (&vf, (double) posMs / 1000.0); + + if (r) { - error( "seek", r ); - return -1; + error ("seek", r); + return -1; } - - posMs = indexMs(); - return posMs; + + posMs = indexMs (); + return posMs; } -int mgOggFile::stream( short *buffer, int samples ) + +int +mgOggFile::stream (short *buffer, int samples) { - int n; - do + int n; + do { - int stream; - n = ov_read( &vf, (char *)buffer, samples*2, 0, 2, 1, &stream ); - } - while( n == OV_HOLE ); - - if(n < 0) + int stream; + n = ov_read (&vf, (char *) buffer, samples * 2, 0, 2, 1, &stream); + } + while (n == OV_HOLE); + + if (n < 0) { - error( "read", n ); + error ("read", n); } - return (n/2); + return (n / 2); } + // --- mgOggDecoder ------------------------------------------------------------- -mgOggDecoder::mgOggDecoder( mgContentItem *item ) - : mgDecoder( item ) +mgOggDecoder::mgOggDecoder (mgContentItem * item):mgDecoder (item) { - m_filename = item->getSourceFile(); - m_file = new mgOggFile( m_filename ); - m_pcm = 0; - init(); + m_filename = item->getSourceFile (); + m_file = new mgOggFile (m_filename); + m_pcm = 0; + init (); } -mgOggDecoder::~mgOggDecoder() + +mgOggDecoder::~mgOggDecoder () { - delete m_file; - clean(); + delete m_file; + clean (); } -bool mgOggDecoder::valid() + +bool mgOggDecoder::valid () { - bool res = false; - if( tryLock() ) - { - if( m_file->open( false ) ) - { - res = true; - } - unlock(); - } - return res; + bool + res = false; + if (tryLock ()) + { + if (m_file->open (false)) + { + res = true; + } + unlock (); + } + return res; } -mgPlayInfo *mgOggDecoder::playInfo(void) + +mgPlayInfo * +mgOggDecoder::playInfo (void) { - if( m_playing ) - { - // m_playinfo.m_index = index/1000; - // m_playinfo.m_total = info.Total; + if (m_playing) + { +// m_playinfo.m_index = index/1000; +// m_playinfo.m_total = info.Total; - return &m_playinfo; - } + return &m_playinfo; + } - return 0; + return 0; } -void mgOggDecoder::init() + +void +mgOggDecoder::init () { - clean(); - m_pcm = new struct mad_pcm; - m_index = 0; + clean (); + m_pcm = new struct mad_pcm; + m_index = 0; } -bool mgOggDecoder::clean() + +bool mgOggDecoder::clean () { - m_playing = false; - - delete m_pcm; - m_pcm = 0; - - m_file->close(); - return false; + 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() +bool mgOggDecoder::start () { - lock(true); - init(); - m_playing = true; + lock (true); + init (); + m_playing = true; - if( m_file->open() /*&& info.DoScan(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() ); - } +// 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; + clean (); + unlock (); + + return false; } -bool mgOggDecoder::stop(void) + +bool mgOggDecoder::stop (void) { - lock(); + lock (); - if( m_playing ) - { - clean(); - } - unlock(); + if (m_playing) + { + clean (); + } + unlock (); - return true; + return true; } -struct mgDecode *mgOggDecoder::done(eDecodeStatus status) + +struct mgDecode * +mgOggDecoder::done (eDecodeStatus status) { - m_ds.status = status; - m_ds.index = m_index; - m_ds.pcm = m_pcm; + m_ds.status = status; + m_ds.index = m_index; + m_ds.pcm = m_pcm; - unlock(); // release the lock from decode() + unlock (); // release the lock from decode() - return &m_ds; + return &m_ds; } -struct mgDecode *mgOggDecoder::decode(void) + +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); + 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 + // from database + m_pcm->samplerate = m_item->getSampleRate (); + 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]; + + // shift value for mad_fixed conversion + const int s = MAD_F_FRACBITS + 1 - (sizeof (short) * 8); + + 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) + +bool mgOggDecoder::skip (int Seconds, int Avail, int Rate) { - lock(); - bool res = false; + lock (); + bool + res = false; - if( m_playing && m_file->canSeek() ) + 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 ); + 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 xDEBUG + int + i = index / 1000; + printf ("ogg: skipping to %02d:%02d\n", i / 60, i % 60); #endif - res = true; - } + res = true; + } } - unlock(); - return res; + unlock (); + return res; } - -#endif //HAVE_VORBISFILE +#endif //HAVE_VORBISFILE diff --git a/vdr_decoder_ogg.h b/vdr_decoder_ogg.h index d249ab2..1f5fcfc 100644 --- a/vdr_decoder_ogg.h +++ b/vdr_decoder_ogg.h @@ -1,9 +1,9 @@ /*! \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 + * Adapted from * MP3/MPlayer plugin to VDR (C++) * (C) 2001-2003 Stefan Huelswitt <huels@iname.com> */ @@ -22,43 +22,42 @@ class mgOggFile; -/*! +/*! * \brief A decoder for Ogg Vorbis files * */ -class mgOggDecoder : public mgDecoder +class mgOggDecoder:public mgDecoder { - private: + private: - mgOggFile *m_file; - struct mgDecode m_ds; - struct mad_pcm *m_pcm; - unsigned long long m_index; + 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 ); +// + void init (void); + bool clean (void); + struct mgDecode *done (eDecodeStatus status); - public: + public: - mgOggDecoder( mgContentItem *item ); - ~mgOggDecoder(); + mgOggDecoder (mgContentItem * item); + ~mgOggDecoder (); - virtual mgPlayInfo *playInfo(); + virtual mgPlayInfo *playInfo (); - virtual bool valid(); + virtual bool valid (); - virtual bool start(); + virtual bool start (); - virtual bool stop(); + virtual bool stop (); - virtual bool skip(int Seconds, int Avail, int Rate); + virtual bool skip (int Seconds, int Avail, int Rate); - virtual struct mgDecode *decode(void); + virtual struct mgDecode *decode (void); }; // ---------------------------------------------------------------- - -#endif //HAVE_VORBISFILE -#endif //___DECODER_OGG_H +#endif //HAVE_VORBISFILE +#endif //___DECODER_OGG_H @@ -1,10 +1,9 @@ -/*! +/*! * \file vdr_menu.c * \brief Implements menu handling for browsing media libraries within VDR * - * \version $Revision: 1.27 $ - * \date $Date$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \version $Revision: 1.27 $ * \date $Date$ + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner, Wolfgang Rohdewald * \author Responsible author: $Author$ * * $Id$ @@ -14,10 +13,6 @@ #include <string> #include <vector> -// #include <cstring> - -#include <mysql/mysql.h> - #include <menuitems.h> #include <tools.h> #include <config.h> @@ -28,965 +23,666 @@ #include <vdr/skins.h> #endif -#include "muggle.h" +#include "vdr_setup.h" #include "vdr_menu.h" #include "vdr_player.h" #include "i18n.h" -#include "mg_content_interface.h" -#include "mg_playlist.h" #define DEBUG #include "mg_tools.h" -#include "mg_media.h" -#include "mg_filters.h" -#include "gd_content_interface.h" - -// ----------------------- mgMenuTreeItem ------------------ +#include <config.h> +#if VDRVERSNUM >= 10307 +#include <vdr/interface.h> +#include <vdr/skins.h> +#endif -mgMenuTreeItem::mgMenuTreeItem( mgSelectionTreeNode *node ) - : m_node( node ) +void +mgStatus::OsdCurrentItem(const char* Text) { - Set(); + cOsdItem* i = main->Get(main->Current()); + if (!i) return; + if (i == IgnoreNextEventOn) + { + IgnoreNextEventOn = NULL; + return; + } + mgAction * a = dynamic_cast<mgAction *>(i); + if (!a) + mgError("mgStatus::OsdCurrentItem expected an mgAction*"); + a->Notify(); +} + +void Play(mgSelection *sel,const bool select) { + mgSelection *s = new mgSelection(sel); + if (select) s->select(); + if (s->empty()) + { + delete s; + return; + } + mgPlayerControl *c = PlayerControl (); + if (c) + c->NewPlaylist (s); + else + cControl::Launch (new mgPlayerControl (s)); } -mgSelectionTreeNode* mgMenuTreeItem::Node() + +//! \brief queue the selection for playing, abort ongoing instant play +void +mgMainMenu::PlayQueue() { - return m_node; + queue_playing=true; + instant_playing=false; + Play(playselection()); } -void mgMenuTreeItem::Set() +//! \brief queue the selection for playing, abort ongoing queue playing +void +mgMainMenu::PlayInstant(const bool select) { - char *buffer = 0; - asprintf( &buffer, m_node->getLabel().c_str() ); - SetText( buffer, false ); + instant_playing=true; + Play(selection(),select); } -// ----------------------- mgMainMenu ---------------------- -mgMainMenu::mgMainMenu(mgMedia *media, mgSelectionTreeNode *root, - mgPlaylist *playlist, cCommands *playlist_commands) - : cOsdMenu( "" ), m_media(media), m_root(root), - m_current_playlist(playlist), m_playlist_commands(playlist_commands) +bool +mgMainMenu::ShowingCollections() { - mgDebug( 1, "Creating Muggle Main Menu" ); - - SetTitle( tr("Muggle Media Database") ); - SetButtons(); + return (UsingCollection && selection ()->level () == 0); +} - DisplayTree( m_root ); - strncpy( m_listname, playlist->getListname().c_str(), 31 ); - m_listname[31] = '\0'; - m_editing_listname = false; +bool +mgMainMenu::DefaultCollectionSelected() +{ + string this_sel = trim(selection ()->getCurrentValue()); + return (ShowingCollections () && this_sel == default_collection); } -mgSelectionTreeNode *mgMainMenu::CurrentNode() + +bool +mgMainMenu::DefaultCollectionEntered() { - mgMenuTreeItem *item = (mgMenuTreeItem *)Get( Current() ); - return item? item->Node(): 0; + if (!UsingCollection) return false; + if (selection()->level()==0) return false; + string collection = trim(selection ()->getKeyValue(0)); + return (collection == default_collection); } -mgMenuTreeItem *mgMainMenu::CurrentItem() -{ - mgMenuTreeItem *item = (mgMenuTreeItem *)Get( Current() ); - return item; +mgMenu * +mgMainMenu::Parent () +{ + if (Menus.size () < 2) + return NULL; + return Menus[Menus.size () - 2]; } -void mgMainMenu::SetButtons( ) + +mgOsdItem* +mgMenu::GenerateAction(const mgActions action) { - SetHasHotkeys(); + mgOsdItem *result = actGenerate(action); + if (result) + { + result->SetMenu(this); + if (!result->Enabled()) + { + delete result; + result=NULL; + } + } + return result; +} - if( m_state == TREE ) - { - SetHelp( tr("Add"), tr("Cycle tree"), tr("Playlist"), tr("Submenu") ); - } - else if( m_state == TREE_SUBMENU ) - { - SetHelp( tr("Instant Play"), tr("2"), tr("3"), tr("Mainmenu") ); - } - else if( m_state == PLAYLIST ) - { - SetHelp( tr("Play"), tr("Move"), tr("Filter"), tr("Submenu") ); - } - else if( m_state == PLAYLIST_SUBMENU ) - { - SetHelp( tr("Load"), tr("Save"), tr("Delete"), tr("Mainmenu") ); - } - else if( m_state == FILTER ) +void +mgMenu::ExecuteAction(const mgActions action) +{ + mgAction *a = GenerateAction (action); + if (a) { - SetHelp( tr("Query"), tr("Other Search"), tr("Browser"), tr("Submenu") ); + a->Execute (); + delete a; } - else - { - SetHelp( "t", "o", "d", "o" ); - } } -void mgMainMenu::Move( int from, int to ) -{ - m_current_playlist->move( from, to ); - cOsdMenu::Move( from, to ); - Display(); +mgPlayerControl * +PlayerControl () +{ + mgPlayerControl *result = NULL; + cControl *control = cControl::Control (); + if (control && typeid (*control) == typeid (mgPlayerControl)) +// is there a running MP3 player? + result = static_cast < mgPlayerControl * >(control); + return result; } -eOSState mgMainMenu::ProcessKey(eKeys key) -{ - MGLOG( "mgMainMenu::ProcessKey" ); - eOSState state = cOsdMenu::ProcessKey(key); - if( m_state == TREE ) - { - mgDebug( 1, "mgMainMenu: in state TREE" ); - // Navigate with up/dn, left/right (pgup, pgdn) - // Expand with OK, Collapse with Back - if( state == osUnknown ) - { - switch( key ) - { - case kOk: - { - m_history.push_back( Current() ); - mgSelectionTreeNode *child = CurrentNode(); - DisplayTree( child ); - - state = osContinue; - } break; - case kRed: - { - mgSelectionTreeNode *current = CurrentNode(); - if( current ) - { - mgDebug( 1, "mgMainMenu: add selection %s to playlist", current->getLabel().c_str() ); - // Add selection to Play - std::vector<mgContentItem*> *tracks = current->getTracks(); - - if( tracks ) - { - - char *buffer = 0; - asprintf( &buffer, tr("%d tracks sent to current playlist"), (int) tracks->size() ); - m_current_playlist->appendList(tracks); -#if VDRVERSNUM >= 10307 - Skins.Message(mtInfo,buffer); - Skins.Flush(); -#else - Interface->Status( buffer ); - Interface->Flush(); -#endif - free( buffer ); - } - else - { - mgDebug(1, "No tracks for current selection" ); - } - } - else - { - mgDebug(1, "Cannot find currently selected node!" ); - } - state = osContinue; - } break; - case kGreen: - { - mgDebug( 1, "mgMainMenu: cycle treeviews (todo)" ); - - state = osContinue; - } break; - case kYellow: - { - mgDebug( 1, "mgMainMenu: cycle to playlist view" ); - - DisplayPlaylist(); - } break; - case kBlue: - { - m_last_osd_index = Current(); - m_menu_item = CurrentItem()->Node(); - DisplayTreeSubmenu(); - - state = osContinue; - } break; - default: - { - state = osContinue; - } break; - } - } - else if( state == osBack ) - { - mgSelectionTreeNode *parent = m_node->getParent(); - - if( parent ) - { - mgDebug( 1, "mgMainMenu: collapse current node" ); - - m_node->collapse(); +mgMenu::mgMenu () +{ + m_osd = NULL; + TreeRedAction = mgActions(0); + TreeGreenAction = mgActions(0); + TreeYellowAction = mgActions(0); + CollRedAction = mgActions(0); + CollGreenAction = mgActions(0); + CollYellowAction = mgActions(0); +} - // restore last selected entry - int last = m_history.back(); - m_history.pop_back(); - DisplayTree( parent, last ); - - state = osContinue; - } - else - { - // Back pressed on root level... Go back to Main VDR menu - state = osBack; +// ----------------------- mgMainMenu ---------------------- - // state = osContinue; - } - } - } - else if( m_state == TREE_SUBMENU ) - { - if( state == osUnknown ) - { - switch( key ) - { - case k0 ... k9: - { - int n = key - k0; - TreeSubmenuAction( n ); - - state = osContinue; - } break; - case kRed: - { - state = TreeSubmenuAction( 0 ); - } break; - case kBlue: - { - m_state = TREE; - - // restore last selected entry - int last = m_history.back(); - DisplayTree( m_node, last ); - - state = osContinue; - } break; - case kOk: - { - state = TreeSubmenuAction( Current() ); - } break; - default: - { - state = osContinue; - } break; - } - } - else if( state == osBack ) - { - m_state = TREE; - // restore last selected entry - int last = m_history.back(); - DisplayTree( m_node, last ); - - state = osContinue; - } - } - else if( m_state == PLAYLIST ) - { - if( state == osUnknown ) - { - switch( key ) - { - case kOk: - { - // start replay at selected index - unsigned idx = Current(); - Play( m_current_playlist, idx ); - state = osContinue; - } break; - case kRed: - { - // TODO: what happens if the user presses play and the player is already active? - // TODO: resume? - unsigned resume = mgMuggle::getResumeIndex(); - Play( m_current_playlist, resume ); - state = osEnd; - } break; - case kGreen: - { - Mark(); - - state = osContinue; - } break; - case kYellow: - { - DisplayFilter(); - state = osContinue; - } break; - case kBlue: - { - // Submenu - m_last_osd_index = Current(); - DisplayPlaylistSubmenu(); - - state = osContinue; - } break; - default: - { - state = osContinue; - }; - } - } - } - else if( m_state == LOAD_PLAYLIST ) - { - if( state == osUnknown ) - { - switch( key ) - { - case kOk: - { - // load the selected playlist - - m_current_playlist -> clear(); - delete m_current_playlist; - - std::string selected = (*m_plists)[ Current() ]; - m_current_playlist = m_media->loadPlaylist( selected.c_str() ); - - // clean the list of playlist - m_plists->clear(); - m_last_osd_index =0; - DisplayPlaylist(0); - state = osContinue; - } break; - default: - { - state = osContinue; - }; - } - } - } - else if( m_state == PLAYLIST_SUBMENU ) - { - if( state == osUnknown ) - { - switch( key ) - { - case k0 ... k9: - { - int n = key - k0; - PlaylistSubmenuAction( n ); - - state = osContinue; - } break; - case kYellow: - { - PlaylistSubmenuAction( 3 ); - } break; - case kBlue: - { - m_state = PLAYLIST; - DisplayPlaylist( m_last_osd_index ); - - state = osContinue; - } break; - case kOk: - { - if( Current() != 0 ) - { // not editing playlist name - state = PlaylistSubmenuAction( Current() ); - } - else - { // editing playlist name - m_editing_listname = !m_editing_listname; - if( m_editing_listname = false ) - { // we just changed from true to false so editing was terminated - RenamePlaylist( std::string( m_listname ) ); - - } - } - } break; - default: - { - state = osContinue; - } break; - } - } - else if( state == osBack ) - { - m_state = PLAYLIST; - DisplayPlaylist( m_last_osd_index ); - - state = osContinue; - } - } - else if( m_state == PLAYLIST_COMMANDS ) - { - if( state == osUnknown ) - { - switch( key ) - { - case kOk: - { - state = ExecutePlaylistCommand( Current() ); - } break; - default: - { - } - } - } - else if( state == osBack ) - { - m_state = PLAYLIST_SUBMENU; - DisplayPlaylistSubmenu(); - - state = osContinue; - } - } - else if( m_state == FILTER ) - { - if( state == osUnknown ) - { - switch( key ) - { - case kRed: // - { - mgDebug( 1, "mgMainMenu: query and display results" ); - - if( m_root ) - { - delete m_root; - } - - m_root = m_media->applyActiveFilter(); - // collapse all? - DisplayTree( m_root ); - - state = osContinue; - } break; - case kGreen: - { - // cycle FILTER -> TREE - mgDebug( 1, "mgMainMenu: next filters " ); - - m_media->nextFilterSet(); - DisplayFilter(); - - state = osContinue; - } break; - case kYellow: - { - // Green: treeview - mgDebug( 1, "mgMainMenu: switch to treeview" ); - - DisplayTreeViewSelector(); - - state = osContinue; - } break; - case kBlue: - { - mgDebug( 1, "mgMainMenu: submenu" ); - state = osContinue; - } break; - default: - { - state = osContinue; - } - } - } - else if( state == osBack ) - { - // m_media->resetFilters();? - } - // RaK: Verhindert, dass die Help Buttons verschwinden, - // ist aber keine schöne Lösung - //SetHelp( tr("Query"), tr("Other Search"), tr("Browser"), tr("Submenu") ); - } - else - { - mgDebug(1, "Process key: else"); - mgDebug(1, "Process key: %d", (int) state); - } - - return state; +void mgMainMenu::SaveState() +{ + char *b; + asprintf(&b,"%s/muggle.state",cPlugin::ConfigDirectory ("muggle")); + FILE *f = fopen(b,"w"); + free(b); + if (!f) return; + mgValmap nmain("MainMenu"); + nmain.put("DefaultCollection",default_collection); + nmain.put("UsingCollection",UsingCollection); + nmain.put("TreeRedAction",int(Menus.front()->TreeRedAction)); + nmain.put("TreeGreenAction",int(Menus.front()->TreeGreenAction)); + nmain.put("TreeYellowAction",int(Menus.front()->TreeYellowAction)); + nmain.put("CollRedAction",int(Menus.front()->CollRedAction)); + nmain.put("CollGreenAction",int(Menus.front()->CollGreenAction)); + nmain.put("CollYellowAction",int(Menus.front()->CollYellowAction)); + mgValmap nsel("tree"); + m_treesel.DumpState(nsel); + mgValmap ncol("collection"); + m_collectionsel.DumpState(ncol); + nmain.Write(f); + nsel.Write(f); + ncol.Write(f); + fclose(f); } -void mgMainMenu::DisplayTree( mgSelectionTreeNode* node, int select ) +mgMainMenu::mgMainMenu ():cOsdMenu ("") { - m_state = TREE; + m_Status = new mgStatus(this); + m_message = NULL; + queue_playing=false; + instant_playing=false; + play_collection = tr("play"); + mgValmap nsel("tree"); + mgValmap ncol("collection"); + mgValmap nmain("MainMenu"); + + // define defaults for values missing in state file: + ncol.put("Keys.0.Choice","Collection"); + ncol.put("Keys.1.Choice","Collection item"); + nmain.put("DefaultCollection",play_collection); + nmain.put("UsingCollection","false"); + nmain.put("TreeRedAction",int(actAddThisToCollection)); + nmain.put("TreeGreenAction",int(actInstantPlay)); + nmain.put("TreeYellowAction",int(actToggleSelection)); + nmain.put("CollRedAction",int(actAddThisToCollection)); + nmain.put("CollGreenAction",int(actInstantPlay)); + nmain.put("CollYellowAction",int(actToggleSelection)); + + // load values from state file + char *b; + asprintf(&b,"%s/muggle.state",cPlugin::ConfigDirectory ("muggle")); + FILE *f = fopen(b,"r"); + free(b); + if (f) { + nsel.Read(f); + ncol.Read(f); + nmain.Read(f); + fclose(f); + } - if( node->expand( ) ) + // get values from mgValmaps + default_collection = nmain.getstr("DefaultCollection"); + UsingCollection = nmain.getbool("UsingCollection"); + InitMapFromSetup(nsel); + m_treesel.InitFrom (nsel); + InitMapFromSetup(ncol); + m_collectionsel.InitFrom (ncol); + m_playsel.InitFrom(ncol); + + // initialize + m_collectionsel.CreateCollection(default_collection); + m_collectionsel.CreateCollection(play_collection); + m_playsel.setKey(0,"Collection"); + m_playsel.setKey(1,"Collection item"); + m_playsel.enter(play_collection); + UseNormalSelection (); + unsigned int posi = selection()->gotoPosition(); + mgMenu *root = new mgTree; + root->TreeRedAction = mgActions(nmain.getuint("TreeRedAction")); + root->TreeGreenAction = mgActions(nmain.getuint("TreeGreenAction")); + root->TreeYellowAction = mgActions(nmain.getuint("TreeYellowAction")); + root->CollRedAction = mgActions(nmain.getuint("CollRedAction")); + root->CollGreenAction = mgActions(nmain.getuint("CollGreenAction")); + root->CollYellowAction = mgActions(nmain.getuint("CollYellowAction")); + AddMenu (root); + + SetCurrent (Get (posi)); + +// Read commands for collections in etc. /video/muggle/playlist_commands.conf + external_commands = new cCommands (); + + char * + cmd_file = (char *) AddDirectory (cPlugin::ConfigDirectory ("muggle"), + "playlist_commands.conf"); + mgDebug (1, "mgMuggle::Start: Looking for file %s", cmd_file); + bool have_cmd_file = external_commands->Load ((const char *) cmd_file); + + if (!have_cmd_file) { - Clear(); - - char buffer[256]; - sprintf( buffer, "Muggle - %s", node->getLabel().c_str() ); - - SetTitle( buffer ); - SetButtons(); - - m_node = node; - std::vector<mgSelectionTreeNode*> children = node->getChildren(); - - for( std::vector<mgSelectionTreeNode*>::iterator iter = children.begin(); - iter != children.end(); - iter ++ ) - { - Add( new mgMenuTreeItem( *iter ) ); - } - - cOsdItem *item = Get( select ); - SetCurrent( item ); - - RefreshCurrent(); - DisplayCurrent(true); + delete external_commands; + external_commands = NULL; } - Display(); + forcerefresh = false; } -void mgMainMenu::DisplayTreeViewSelector() +mgMainMenu::~mgMainMenu() { - m_history.clear(); - // collapse all! - DisplayTree( m_root ); + delete m_Status; } -void mgMainMenu::DisplayTreeSubmenu() +void +mgMainMenu::InitMapFromSetup (mgValmap& nv) { - m_state = TREE_SUBMENU; - - Clear(); - SetButtons(); + // values from setup override saved values + nv["Host"] = the_setup.DbHost; + nv["User"] = the_setup.DbUser; + nv["Password"] = the_setup.DbPass; + nv["Directory"] = cPlugin::ConfigDirectory ("muggle"); + nv["ToplevelDir"] = the_setup.ToplevelDir; +} - char *buffer; - asprintf( &buffer, "Muggle - %s", tr("Tree View Commands") ); - SetTitle( buffer ); - free( buffer ); +void +mgMenu::AddAction (const mgActions action, const bool hotkey) +{ + mgOsdItem *a = GenerateAction(action); + if (!a) return; + const char *mn = a->MenuName(); + if (strlen(mn)==0) + mgError("AddAction(%d):MenuName is empty",int(action)); + if (hotkey) + a->SetText(osd()->hk(mn)); + else + a->SetText(mn); + free(const_cast<char*>(mn)); + osd()->Add(a); +} - // Add items - Add( new cOsdItem( "Instant play" ) ); - Display(); +void +mgMenu::AddExternalAction(const mgActions action, const char *title) +{ + mgOsdItem *a = GenerateAction(action); + if (!a) return; + a->SetText(osd()->hk(title)); + osd()->Add(a); } -eOSState mgMainMenu::TreeSubmenuAction( int n ) +void +mgMenu::AddSelectionItems () { - eOSState state = osContinue; - - switch( n ) + for (unsigned int i = 0; i < selection()->values.size (); i++) + { + mgOsdItem *a = GenerateAction(actEntry); + if (!a) continue; + a->SetText(a->MenuName(i+1,selection()->values[i]),false); + osd()->Add(a); + } + if (osd()->ShowingCollections ()) { - case 0: - { - // action 0: instant play of current node, might need a security question - - mgSelectionTreeNode *current = CurrentNode(); - if( current ) - { - // append current node - std::vector<mgContentItem*> *tracks = m_menu_item->getTracks(); - - if( tracks ) - { - // clear playlist - m_current_playlist->clear(); - m_current_playlist->appendList( tracks ); - - // play - mgMuggle::setResumeIndex( 0 ); - Play( m_current_playlist, 0 ); - - state = osEnd; - } - } - } break; - case 1: - { - // action 0 - } break; - default: - { - // undefined action - } break; + mgCreateCollection *a = new mgCreateCollection; + if (!a) return; + a->SetMenu(this); + if (!a->Enabled()) + { + delete a; + a=NULL; + } + if (!a) return; + a->SetText(a->MenuName(),false); + osd()->Add(a); } - - return state; } -void mgMainMenu::DisplayPlaylist( int index_current ) -{ - m_state = PLAYLIST; - - // make sure we have a current playlist - Clear(); - SetButtons(); - std::vector<mgContentItem*>* list = m_current_playlist-> getAll(); - static char titlestr[80]; - sprintf( titlestr, "Muggle - %s (%d %s)",tr("Playlist"), - list->size() , - tr("items") ); - SetTitle( titlestr ); +const char * +mgMenu::BlueName () +{ + if (typeid (*this) == typeid (mgTree)) + return tr("Commands"); + else + return tr ("List"); +} - for( unsigned int i = 0; i < m_current_playlist->getNumItems(); i++) +void +mgMenu::SetHelpKeys() +{ + const char *Red = NULL; + const char *Green = NULL; + const char *Yellow = NULL; + mgOsdItem *a; + if (osd()->UsingCollection) { - std::string label = m_current_playlist->getLabel( i, " " ); - Add( new cOsdItem( label.c_str() ) ); + if ((a = GenerateAction (CollRedAction))) + Red = a->ButtonName (); + if ((a = GenerateAction (CollGreenAction))) + Green = a->ButtonName (); + if ((a = GenerateAction (CollYellowAction))) + Yellow = a->ButtonName (); } - - if( index_current >= 0 ) + else { - cOsdItem *item = Get( m_last_osd_index ); - SetCurrent( item ); - RefreshCurrent(); - DisplayCurrent( true ); - } - - Display(); -} - -void mgMainMenu::LoadPlaylist() -{ - m_state = LOAD_PLAYLIST; - static char titlestr[80]; - - // make sure we have a current playlist - Clear(); - SetButtons(); - sprintf( titlestr, "Muggle - %s %s ",tr("load"), tr("Playlist")); - SetTitle( titlestr ); - - // retrieve list of available playlists - m_plists = m_media->getStoredPlaylists(); - - for(std::vector<std::string>::iterator iter = m_plists->begin(); - iter != m_plists->end() ; iter++) - { - Add( new cOsdItem( iter->c_str() ) ); + if ((a = GenerateAction (TreeRedAction))) + Red = a->ButtonName (); + if ((a = GenerateAction (TreeGreenAction))) + Green = a->ButtonName (); + if ((a = GenerateAction (TreeYellowAction))) + Yellow = a->ButtonName (); } - - Display(); + osd()->SetHelpKeys(Red,Green,Yellow,BlueName()); } -void mgMainMenu::SavePlaylist() -{ - if(m_current_playlist->getListname() == "") - { - // create dummy listname with current date and time - time_t currentTime = time(NULL); - m_current_playlist->setListname(ctime(¤tTime)); - } - m_current_playlist->storePlaylist(); -} -void mgMainMenu::RenamePlaylist( std::string name ) +void +mgMenu::InitOsd (const char *title,const bool hashotkeys) { - // dummy function. USes current date as name - m_current_playlist->setListname( name ); - - // confirmation -#if VDRVERSNUM >= 10307 - Skins.Message(mtInfo, "Playlist renamed" ); - Skins.Flush(); -#else - Interface->Status( "Playlist renamed" ); - Interface->Flush(); -#endif + osd ()->InitOsd (title,hashotkeys); } -void mgMainMenu::DisplayPlaylistSubmenu() + +void +mgMainMenu::InitOsd (const char *title,const bool hashotkeys) { - static const char allowed[] = { "abcdefghijklmnopqrstuvwxyz0123456789-_" }; + Clear (); + SetTitle (title); + if (hashotkeys) SetHasHotkeys (); +} - m_state = PLAYLIST_SUBMENU; - Clear(); - SetButtons(); - SetTitle( "Muggle - Playlist View Commands" ); +void +mgSubmenu::BuildOsd () +{ + static char b[100]; + snprintf(b,99,tr("Commands:%s"),trim(osd()->selection()->getCurrentValue()).c_str()); + InitOsd (b); + mgMenu *p = osd ()->Parent (); + if (!p) + return; + AddAction(actInstantPlay); + AddAction(actAddThisToCollection); + AddAction(actRemoveThisFromCollection); + AddAction(actToggleSelection); + AddAction(actSetDefault); + AddAction(actDeleteCollection); + AddAction(actChooseSearch); + AddAction(actExportTracklist); + cCommand *command; + if (osd()->external_commands) + { + int idx=0; + while ((command = osd ()->external_commands->Get (idx)) != NULL) + { + if (idx>actExternalHigh-actExternal0) + { + mgWarning("Too many external commands"); + break; + } + AddExternalAction (mgActions(idx+int(actExternal0)),command->Title()); + idx++; + } + } + TreeRedAction = actSetButton; + TreeGreenAction = actSetButton; + TreeYellowAction = actSetButton; + CollRedAction = actSetButton; + CollGreenAction = actSetButton; + CollYellowAction = actSetButton; +} - // Add items - Add( new cMenuEditStrItem( tr("Playlist name"), m_listname, 31, allowed ) ); - Add( new cOsdItem( tr("Load playlist" ) ) ); - Add( new cOsdItem( tr("Save playlist" ) ) ); - Add( new cOsdItem( tr("Clear playlist" ) ) ); - Add( new cOsdItem( tr("Remove entry from list" ) ) ); - Add( new cOsdItem( tr("Export playlist" ) ) ); - if( m_playlist_commands ) +eOSState +mgTree::Process (eKeys key) +{ + eOSState result = osUnknown; + switch (key) { - Add( new cOsdItem( tr("External playlist commands" ) ) ); + case kRed: + if (osd()->UsingCollection) + ExecuteAction (CollRedAction); + else + ExecuteAction (TreeRedAction); + return osContinue; + case kGreen: + if (osd()->UsingCollection) + ExecuteAction (CollGreenAction); + else + ExecuteAction (TreeGreenAction); + return osContinue; + case kYellow: + if (osd()->UsingCollection) + ExecuteAction (CollYellowAction); + else + ExecuteAction (TreeYellowAction); + return osContinue; + default: + result = osUnknown; + break; } + return result; +} - Display(); +void +mgTree::BuildOsd () +{ + InitOsd (selection ()->getListname ().c_str (), false); + AddSelectionItems (); } -void mgMainMenu::DisplayPlaylistCommands() +void +mgMainMenu::Message1(const char *msg, const char *arg1) { - m_state = PLAYLIST_COMMANDS; + if (strlen(msg)==0) return; + asprintf (&m_message, tr (msg), arg1); +} - cCommand *command; - int i = 0; - Clear(); - SetTitle( "Muggle - External Playlist Commands" ); +eOSState mgMainMenu::ProcessKey (eKeys key) +{ + eOSState result = osContinue; + if (key!=kNone) + mgDebug (3, "MainMenu::ProcessKey(%d)", (int) key); - while( ( command = m_playlist_commands->Get(i) ) != NULL ) + if (Menus.size()<1) + mgError("mgMainMenu::ProcessKey: Menus is empty"); + + mgPlayerControl * c = PlayerControl (); + if (c) { - Add( new cOsdItem( hk( command->Title() ) ) ); - i++; + if (!c->Active ()) + { + c->Shutdown (); + if (instant_playing && queue_playing) { + PlayQueue(); + } + else + { + instant_playing = false; + queue_playing = false; + } + } + else + { + switch (key) + { + case kPause: + c->Pause (); + break; + case kStop: + if (instant_playing && queue_playing) { + PlayQueue(); + } + else + { + queue_playing = false; + c->Stop (); + } + break; + case kChanUp: + c->Forward (); + break; + case kChanDn: + c->Backward (); + break; + default: + goto otherkeys; + } + goto pr_exit; + } } + else + if (key==kPlay) { + PlayQueue(); + goto pr_exit; + } +otherkeys: + newmenu = Menus.back(); // Default: Stay in current menu + newposition = -1; - Display(); -} - -eOSState mgMainMenu::ExecutePlaylistCommand( int current ) -{ - cCommand *command = m_playlist_commands->Get( current ); - if( command ) { - char *buffer = NULL; - bool confirmed = true; - if( command->Confirm() ) - { - asprintf( &buffer, "%s?", command->Title() ); -//#if VDRVERSNUM < 10307 - confirmed = Interface->Confirm( buffer ); -//#else -//#endif - free( buffer ); - } - if( confirmed ) - { - asprintf( &buffer, "%s...", command->Title() ); -#if VDRVERSNUM >= 10307 - Skins.Message(mtInfo,buffer); - Skins.Flush(); -#else - Interface->Status( buffer ); - Interface->Flush(); -#endif - free( buffer ); + mgMenu * oldmenu = newmenu; - std::string tmp_m3u_file = (char *) AddDirectory( cPlugin::ConfigDirectory("muggle"), "current.m3u" ); - m_current_playlist->exportM3U( tmp_m3u_file ); +// item specific key logic: + result = cOsdMenu::ProcessKey (key); - char *result = (char *)command->Execute( tmp_m3u_file.c_str() ); +// mgMenu specific key logic: + if (result == osUnknown) + result = oldmenu->Process (key); + } +// catch osBack for empty OSD lists +// (because if the list was empty, no mgOsdItem::ProcessKey was ever called) + if (result == osBack) + { + // do as if there was an entry + mgOsdItem *a = Menus.back()->GenerateAction(actEntry); + if (a) + { + result = a->Back(); + delete a; + } + } - /* What to do? Recode cMenuText (not much)? - if( result ) - { - return AddSubMenu( new cMenuText( command->Title(), result ) ); - } - */ - - free( result ); +// do nothing for unknown keys: + if (result == osUnknown) + goto pr_exit; - return osEnd; - } +// change OSD menu as requested: + if (newmenu == NULL) + { + if (Menus.size () > 1) + { + Menus.pop_back (); + forcerefresh = true; + } + else + { + result = osBack; // game over + goto pr_exit; + } } - return osContinue; -} - -eOSState mgMainMenu::PlaylistSubmenuAction( int n ) -{ - std::cout << "mgMainMenu::PlaylistSubmenuAction: " << n << std::endl << std::flush; - eOSState state = osContinue; - - switch( n ) + else if (newmenu != Menus.back ()) { - case 0: - { // rename playlist - should never get here! - state = osContinue; - } break; - - case 1: - { - LoadPlaylist(); -#if VDRVERSNUM < 10307 - Interface->Flush(); -#else - Skins.Flush(); -#endif - // jump to playlist view from here? - } break; - case 2: - { - SavePlaylist(); -#if VDRVERSNUM >= 10307 - Skins.Message(mtInfo,"Playlist saved"); - Skins.Flush(); -#else - Interface->Status( "Playlist saved"); - Interface->Flush(); -#endif - } break; - case 3: - { // clear playlist + AddMenu (newmenu); + } - cControl *control = cControl::Control(); - std::string buffer; + if (UsingCollection) + forcerefresh |= m_collectionsel.cacheIsEmpty(); + else + forcerefresh |= m_treesel.cacheIsEmpty(); - if( control && typeid(*control) == typeid(mgPlayerControl) ) - { - buffer = "Cannot clear playlist while playing."; - } - else - { - m_current_playlist->clear(); + forcerefresh |= (newposition>=0); - buffer = "Playlist cleared"; - } - - // confirmation -#if VDRVERSNUM >= 10307 - Skins.Message( mtInfo, buffer.c_str() ); - Skins.Flush(); -#else - Interface->Status( buffer.c_str() ); - Interface->Flush(); -#endif - - state = osContinue; - } break; - case 4: - { // remove selected title - bool res = m_current_playlist->remove( m_last_osd_index ); - - if( m_last_osd_index > 0 ) - { - m_last_osd_index --; - } - DisplayPlaylist( m_last_osd_index ); - - // confirmation - std::string confirm = res? "Entry deleted": "Cannot delete entry"; + if (forcerefresh) + { + mgDebug(2,"forced refresh"); + forcerefresh = false; + if (newposition<0) + newposition = selection()->gotoPosition(); + Menus.back ()->Display (newposition); + } +pr_exit: + showMessage(); + return result; +} +void +mgMainMenu::showMessage() +{ + if (m_message) + { #if VDRVERSNUM >= 10307 - Skins.Message( mtInfo, confirm.c_str() ); - Skins.Flush(); + Skins.Message (mtInfo, m_message); + Skins.Flush (); #else - Interface->Status( confirm.c_str() ); - Interface->Flush(); + Interface->Status (m_message); + Interface->Flush (); #endif - } break; - case 5: - { - std::string m3u_file = AddDirectory( cPlugin::ConfigDirectory("muggle"), - m_current_playlist->getListname().c_str() ); - m_current_playlist->exportM3U( m3u_file ); - } break; - case 6: - { - DisplayPlaylistCommands(); - } break; - default: - { - // undefined action - } break; + free(m_message); + m_message = NULL; } - - return state; } -void mgMainMenu::DisplayFilter() -{ - m_state = FILTER; - Clear(); - SetButtons(); +void +mgMainMenu::AddMenu (mgMenu * m) +{ + Menus.push_back (m); + m->setosd (this); + m->Display (0); +} - SetTitle( m_media->getActiveFilterTitle().c_str() ); - std::vector<mgFilter*> *filter_list = m_media->getActiveFilters(); - - int i=0; - for( std::vector<mgFilter*>::iterator iter = filter_list->begin(); - iter != filter_list->end(); - iter ++ ) - { - mgDebug( 1, "Filter %d/%dint filter %s='%s'", - i, filter_list->size(), - (*iter)->getName(), - (*iter)->getStrVal().c_str()); - switch( (*iter)->getType() ) - { - case mgFilter::INT: - { - mgFilterInt *fi = (mgFilterInt *) (*iter); - - Add( new cMenuEditIntItem( fi->getName(), - &(fi->m_intval), - fi->getMin(), fi->getMax() ) ); - } break; - case mgFilter::STRING: - { - mgFilterString *fs = (mgFilterString *) (*iter); - - // BUG: This might be buggy as fs->getAllowedChars() may become - // invalid while VDR is still trying to access it - Add( new cMenuEditStrItem( fs->getName(), fs->m_strval, - fs->getMaxLength(), - fs->getAllowedChars().c_str() ) ); - - } break; - case mgFilter::BOOL: - { - mgFilterBool *fb = (mgFilterBool *) (*iter); - Add( new cMenuEditBoolItem( fb->getName(), &( fb->m_bval), - fb->getTrueString().c_str(), - fb->getFalseString().c_str() ) ); - } break; - case mgFilter::CHOICE: - { - mgFilterChoice *fc = (mgFilterChoice *) (*iter); - std::vector<std::string> choices = fc->getChoices(); - - char **choices_str = new (char *)[ choices.size() ]; - - int j = 0; - for( std::vector<std::string>::iterator iter = choices.begin(); - iter != choices.end(); - iter ++, j ++ ) - { - // BUG: Is this a big memory leak!? When to delete and who? - // RaK: zweiter Iterator war "i" richtig: "j" - // choices_str[j] = strndup( choices[i].c_str(), 128 ); - choices_str[j] = strndup( choices[j].c_str(), 128 ); - } - - Add( new cMenuEditStraItem( fc->getName(), &( fc->m_selval ), - choices.size(), choices_str ) ); - - // delete all choices_str elements! - // delete[] choices_str; // ??? - - } break; - default: - case mgFilter::UNDEF: - { - } break; - } - i++; - } - - Display(); +eOSState +mgSubmenu::Process (eKeys key) +{ + return osUnknown; } -void mgMainMenu::DisplayFilterSelector() + +void +mgTreeViewSelector::BuildOsd () { - // show available filters, load on OK? + InitOsd (tr ("Tree View Selection")); + AddAction(actSearchCollItem); + AddAction(actSearchArtistAlbumTitle); + AddAction(actSearchArtistTitle); + AddAction(actSearchAlbumTitle); + AddAction(actSearchGenreYearTitle); + AddAction(actSearchGenreArtistAlbumTitle); } -void mgMainMenu::Play( mgPlaylist *plist, unsigned first ) +void +mgMainMenu::DisplayGoto (unsigned int select) { - MGLOG( "mgMainMenu::Play" ); - cControl *control = cControl::Control(); - - if( control && typeid(*control) == typeid(mgPlayerControl) ) - { // is there a running MP3 player? - static_cast<mgPlayerControl*>(control)->NewPlaylist(plist, first); // signal the running player to load the new playlist - } - else + if (select >= 0) { - cControl::Launch( new mgPlayerControl(plist, first) ); + SetCurrent (Get (select)); + RefreshCurrent (); } + Display (); + DisplayMenu()->SetTabs(25); +} + + +void +mgMenu::Display (const unsigned int position) +{ + BuildOsd (); + osd ()->DisplayGoto (position); } + @@ -1,10 +1,10 @@ -/*! +/*! * \file vdr_menu.h * \brief Implements menu handling for broswing media libraries within VDR * * \version $Revision: 1.13 $ * \date $Date$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner, Wolfgang Rohdewald * \author Responsible author: $Author$ * * $Id$ @@ -18,106 +18,324 @@ #include <vector> #include <osd.h> +#include <plugin.h> +#include <status.h> #include "i18n.h" +#include "mg_actions.h" + +#include "vdr_player.h" + +using namespace std; + +//! \brief play a selection, aborting what is currently played +//! \param select if true, play only what the current position selects +void Play(mgSelection *sel,const bool select=false); class cCommands; -class mgMedia; -class mgSelectionTreeNode; -class mgPlaylist; -class mgTracklist; +class mgSelection; +class mgMenu; +class mgMainMenu; + +//! \brief if a player is running, return it +mgPlayerControl * PlayerControl (); + +//! \brief callback class, monitors state changes in vdr +class mgStatus : public cStatus +{ + private: + //! \brief the mgMainMenu that wants to be notified + mgMainMenu *main; + public: + //! \brief default constructor + mgStatus(mgMainMenu* m) { main = m;IgnoreNextEventOn=NULL;} + /*! \brief vdr calls OsdCurrentItem more often than we + * want. This tells mgStatus to ignore the next call + * for a specific item. + * \todo is this behaviour intended or a bug in vdr + * or in muggle ? + */ + cOsdItem* IgnoreNextEventOn; + protected: + //! \brief the event we want to know about + virtual void OsdCurrentItem(const char *Text); +}; + /*! - * \brief a special menu item + * \brief the muggle main OSD */ -class mgMenuTreeItem : public cOsdItem + +class mgMainMenu:public cOsdMenu { - public: + private: + mgSelection m_treesel; + mgSelection m_playsel; + mgSelection m_collectionsel; + char *m_message; + void showMessage(); + public: + //! \brief syntactic sugar: expose the protected cOsdMenu::SetHelp + void SetHelpKeys(const char *Red,const char *Green, const char *Yellow, const char *Blue) { SetHelp(Red,Green,Yellow,Blue); } + + //! \brief callback object, lets vdr notify us about OSD changes + mgStatus *m_Status; + + //! \brief play the play selection, abort ongoing instant play + void PlayQueue(); + + //! \brief instant play the selection, abort ongoing queue playing + void PlayInstant(const bool select=false); + + //! \brief true if we are browsing m_collectionsel + bool UsingCollection; + + //! \brief the different menus, the last one is active + vector < mgMenu * >Menus; + + //! \brief true if an item from the "playing" selection is being played + bool queue_playing; + + //! \brief true if an item is being instant played + bool instant_playing; + + //! \brief parent menu if any + mgMenu * Parent (); + + //! \brief default constructor + mgMainMenu (); + + //! \brief default destructor + ~mgMainMenu (); + + //! \brief save the entire muggle state + void SaveState(); + + //! \brief adds a new mgMenu to the stack + void AddMenu (mgMenu * m); + + //! \brief initializes using values from nv + void InitMapFromSetup (mgValmap& nv); + + //! \brief main entry point, called from vdr + eOSState ProcessKey (eKeys Key); + + //! \brief from now on use the normal selection + void UseNormalSelection () + { + UsingCollection= false; + } + + //! \brief from now on use the collection selection + void UseCollectionSelection () + { + UsingCollection= true; + } + + //! \brief this is the collection things will be added to + string default_collection; + +/*! \brief this is the "now playing" collection translated in + * the current language. When changing the OSD language, this + * collection will NOT be renamed in the data base, but a new + * empty collection will be started. The collection for the + * previous language will stay, so the user can copy from the + * old one to the new one. + */ + string play_collection; + +/*! \brief selects a certain line on the OSD and displays the OSD + * \param select the line that we want to be selected + */ + void DisplayGoto (unsigned int select); + + //! \brief external commands + cCommands *external_commands; + + //! \brief Actions can set newmenu which will then be displayed next + mgMenu *newmenu; + + //! \brief Actions can set newstate which will then be returned to main vdr + eOSState newstate; - mgMenuTreeItem( mgSelectionTreeNode *node ); + //! \brief Actions can set forcerefresh. This will force a redisplay of the OSD + bool forcerefresh; - mgSelectionTreeNode *Node(); + //! \brief show a message. Can be called by actions. It will + // only be shown at the end of the next mgMainMenu::ProcessKey + // because that might do forcerefresh which overwrites the message + void Message (const char *msg) { m_message = strdup(msg); } + void Message1 (const char *msg, const char *arg1); + void Message1 (const char *msg, string arg1) { Message1(msg,arg1.c_str()); } - void Set(); + //! \brief Actions can request a new position. -1 means none wanted + int newposition; - private: + //! \brief clears the screen, sets a title and buttons with text on them + void InitOsd (const char *title,const bool hashotkeys); - mgSelectionTreeNode *m_node; + //! \brief expose the protected DisplayMenu() from cOsdMenu + cSkinDisplayMenu *DisplayMenu(void) + { + return cOsdMenu::DisplayMenu(); + } + + //! \brief expose the protected cOsdMenu::hk() + const char *hk (const char *s) + { + return cOsdMenu::hk (s); + } + + //! \brief the current selection + mgSelection* selection () + { + if (UsingCollection) + return &m_collectionsel; + else + return &m_treesel; + } + + //! \brief the collection selection + mgSelection* collselection() + { + return &m_collectionsel; + } + +//! \brief the "now playing" selection + mgSelection* playselection () + { + return &m_playsel; + } + +//! \brief true if the cursor is placed in the collection list + bool ShowingCollections(); + +//! \brief true if the cursor is placed on the default collection + bool DefaultCollectionSelected(); + +//! \brief true if the cursor is placed in the default collection + bool DefaultCollectionEntered(); }; -/*! - * \brief the muggle main OSD +//! \brief a generic muggle menu +class mgMenu +{ + private: + mgMainMenu* m_osd; + protected: +//! \brief adds the wanted action to the OSD menu +// \param hotkey if true, add this as a hotkey + void AddAction(const mgActions action, const bool hotkey=true); + + //! \brief add an external action, always with hotkey + void AddExternalAction(const mgActions action, const char *title); + +//! \brief adds entries for all selected data base items to the OSD menu. +// If this is the list of collections, appends a command for collection +// creation. + void AddSelectionItems (); + //! \brief the name of the blue button depends of where we are + const char *mgMenu::BlueName (); + public: + /*! sets the correct help keys. + * \todo without data from mysql, no key is shown, + * not even yellow or blue + */ + void SetHelpKeys(); +//! \brief generates an object for the wanted action + mgOsdItem* GenerateAction(const mgActions action); + +//! \brief executes the wanted action + void ExecuteAction (const mgActions action); + +//! \brief sets the pointer to the owning mgMainMenu + void setosd (mgMainMenu* osd) + { + m_osd = osd; + } + +//! \brief the pointer to the owning mgMainMenu + mgMainMenu* osd () + { + return m_osd; + } + +//! \brief the currently active selection of the owning mgMainMenu + mgSelection* selection () + { + return osd ()->selection (); + } +//! \brief the playselection of the owning mgMainMenu + mgSelection* playselection () + { + return osd ()->playselection (); + } + + mgMenu (); + + virtual ~ mgMenu () + { + } + +//! \brief clears the screen, sets a title and buttons with text on them + void InitOsd (const char *title,const bool hashotkeys=true); + +//! \brief display OSD and go to position + void Display (const unsigned int position); + +//! \brief BuildOsd() should be abstract but then we cannot compile + virtual void BuildOsd () + { + } + +/*! \brief Process() should be abstract but then we cannot compile. + * \return Process may decide that we want another screen to be displayed. + * If the mgMenu* returned is not "this", the caller will use the return + * value for a new display. If NULL is returned, the caller will display + * the previous menu. */ -class mgMainMenu : public cOsdMenu + virtual eOSState Process (eKeys Key) + { + return osUnknown; + } + +//! \brief the ID of the action defined by the red button. + mgActions TreeRedAction; + mgActions CollRedAction; + +//! \brief the ID of the action defined by the green button. + mgActions TreeGreenAction; + mgActions CollGreenAction; + +//! \brief the action defined by the yellow button. + mgActions TreeYellowAction; + mgActions CollYellowAction; +}; + +//! \brief an mgMenu class for navigating through the data base +class mgTree:public mgMenu +{ + public: + eOSState Process (eKeys Key); + protected: + void BuildOsd (); +}; + +//! \brief an mgMenu class for submenus +class mgSubmenu:public mgMenu +{ + public: + eOSState Process (eKeys Key); + protected: + void BuildOsd (); +}; + +//! \brief an mgMenu class for selecting a search view +class mgTreeViewSelector:public mgMenu { - public: - - mgMainMenu(mgMedia *media, mgSelectionTreeNode *root, - mgPlaylist *playlist, cCommands *playlist_commands ); - - mgSelectionTreeNode *CurrentNode(); - mgMenuTreeItem *CurrentItem(); - - eOSState ProcessKey(eKeys Key); - - protected: - - enum MuggleStatus - { - TREE, TREE_SUBMENU, - PLAYLIST, LOAD_PLAYLIST, SAVE_PLAYLIST, - PLAYLIST_SUBMENU, PLAYLIST_COMMANDS, - FILTER, FILTER_SUBMENU - }; - - void SetButtons(); - void Move( int from, int to ); - - // Tree view handling - void DisplayTree( mgSelectionTreeNode *node, int select = 0 ); - void DisplayTreeViewSelector(); - void DisplayTreeSubmenu(); - eOSState TreeSubmenuAction( int n ); - - // Playlist view handling - void DisplayPlaylist( int index_current = -1 ); - void DisplayTrackInfo(); - void DisplayAlbumInfo(); - - void LoadPlaylist(); - void SavePlaylist(); - void RenamePlaylist( std::string name ); - void DisplayPlaylistSubmenu(); - eOSState PlaylistSubmenuAction( int n ); - void DisplayPlaylistCommands(); - eOSState ExecutePlaylistCommand( int current ); - - // Filter view handling - void DisplayFilter(); - void DisplayFilterSelector(); - - private: - //! \brief launch the actual player - void Play( mgPlaylist *plist, unsigned first ); - - // content stuff - mgMedia *m_media; - mgSelectionTreeNode *m_root; - mgSelectionTreeNode *m_node; - mgSelectionTreeNode *m_menu_item; - mgPlaylist *m_current_playlist; - std::vector< std::string > *m_plists; - - MuggleStatus m_state; - std::list< int > m_history; - - cCommands *m_playlist_commands; - - int m_last_osd_index; - - char m_listname[32]; - bool m_editing_listname; + protected: + void BuildOsd (); }; #endif diff --git a/vdr_network.h b/vdr_network.h index 182fa23..4725d2a 100644 --- a/vdr_network.h +++ b/vdr_network.h @@ -15,31 +15,33 @@ class cRingBufferLinear; // ---------------------------------------------------------------- -class mgNet : public cRingBufferLinear, cThread +class mgNet:public cRingBufferLinear, cThread { -private: - int m_fd; - bool m_connected, m_netup; - int m_deferedErrno; - int m_rwTimeout, m_conTimeout; - unsigned char m_lineBuff[4096]; - int m_count; - // - void close(void); - int ringRead(unsigned char *dest, int len); - void copyFromBuff(unsigned char *dest, int n); -protected: - virtual void action(void); -public: - mgNet(int size, int ConTimeoutMs, int RwTimeoutMs); - ~mgNet(); - bool connect(const char *hostname, const int port); - void disconnect(void); - bool connected(void) { return m_connected; } - int gets(char *dest, int len); - int puts(char *dest); - int read(unsigned char *dest, int len); - int write(unsigned char *dest, int len); + private: + int m_fd; + bool m_connected, m_netup; + int m_deferedErrno; + int m_rwTimeout, m_conTimeout; + unsigned char m_lineBuff[4096]; + int m_count; +// + void close (void); + int ringRead (unsigned char *dest, int len); + void copyFromBuff (unsigned char *dest, int n); + protected: + virtual void action (void); + public: + mgNet (int size, int ConTimeoutMs, int RwTimeoutMs); + ~mgNet (); + bool connect (const char *hostname, const int port); + void disconnect (void); + bool connected (void) + { + return m_connected; + } + int gets (char *dest, int len); + int puts (char *dest); + int read (unsigned char *dest, int len); + int write (unsigned char *dest, int len); }; - -#endif //___NETWORK_H +#endif //___NETWORK_H diff --git a/vdr_player.c b/vdr_player.c index 48182ef..b281746 100644 --- a/vdr_player.c +++ b/vdr_player.c @@ -4,12 +4,12 @@ * * \version $Revision: 1.7 $ * \date $Date$ - * \author Ralf Klueber, Lars von Wedel, Andreas Kellner + * \author Ralf Klueber, Lars von Wedel, Andreas Kellner, Wolfgang Rohdewald * \author Responsible author: $Author$ * * $Id$ * - * Adapted from + * Adapted from * MP3/MPlayer plugin to VDR (C++) * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> */ @@ -27,7 +27,6 @@ #include <iostream> #include <mad.h> -#include <id3tag.h> #include <player.h> #include <device.h> @@ -37,45 +36,44 @@ #include <recording.h> #include <status.h> -#include "muggle.h" #include "vdr_player.h" #include "vdr_decoder.h" #include "vdr_config.h" #include "vdr_setup.h" #include "i18n.h" +#include "mg_db.h" #include "mg_tools.h" -#include "mg_playlist.h" -#include "mg_content_interface.h" - // ---------------------------------------------------------------- // TODO: check for use of constants -#define OUT_BITS 16 // output 16 bit samples to DVB driver -#define OUT_FACT (OUT_BITS/8*2) // output factor is 16 bit & 2 channels -> 4 bytes +#define OUT_BITS 16 // output 16 bit samples to DVB driver +#define OUT_FACT (OUT_BITS/8*2) // output factor is 16 bit & 2 channels -> 4 bytes // cResample -#define MAX_NSAMPLES (1152*7) // max. buffer for resampled frame +#define MAX_NSAMPLES (1152*7) // max. buffer for resampled frame // cNormalize -#define MAX_GAIN 3.0 // max. allowed gain -#define LIM_ACC 12 // bit, accuracy for lookup table -#define F_LIM_MAX (mad_fixed_t)((1<<(MAD_F_FRACBITS+2))-1) // max. value covered by lookup table -#define LIM_SHIFT (MAD_F_FRACBITS-LIM_ACC) // shift value for table lookup -#define F_LIM_JMP (mad_fixed_t)(1<<LIM_SHIFT) // lookup table jump between values +#define MAX_GAIN 3.0 // max. allowed gain +#define LIM_ACC 12 // bit, accuracy for lookup table + // max. value covered by lookup table +#define F_LIM_MAX (mad_fixed_t)((1<<(MAD_F_FRACBITS+2))-1) +#define LIM_SHIFT (MAD_F_FRACBITS-LIM_ACC) // shift value for table lookup +#define F_LIM_JMP (mad_fixed_t)(1<<LIM_SHIFT) // lookup table jump between values // cLevel -#define POW_WIN 100 // window width for smoothing power values -#define EPSILON 0.00000000001 // anything less than EPSILON is considered zero +#define POW_WIN 100 // window width for smoothing power values +#define EPSILON 0.00000000001 // anything less than EPSILON is considered zero // cMP3Player -#define MAX_FRAMESIZE 2048 // max. frame size allowed for DVB driver +#define MAX_FRAMESIZE 2048 // max. frame size allowed for DVB driver #define HDR_SIZE 9 #define LPCM_SIZE 7 #define LEN_CORR 3 -#define SPEEDCHECKSTART ((MP3BUFSIZE*1000/32000/2/2)+1000) // play time to fill buffers before speed check starts (ms) -#define SPEEDCHECKTIME 3000 // play time for speed check (ms) + // play time to fill buffers before speed check starts (ms) +#define SPEEDCHECKSTART ((MP3BUFSIZE*1000/32000/2/2)+1000) +#define SPEEDCHECKTIME 3000 // play time for speed check (ms) /* struct LPCMHeader { int id:8; // id @@ -88,21 +86,20 @@ struct LPCMHeader { int id:8; // id int quant_wlen:2; // quantization word length int sample_freq:2; // audio sampling frequency (48khz=0, 96khz=1, 44,1khz=2, 32khz=3) bool reserved2:1; // reserved - int chan_count:3; // number of audio channels - 1 (e.g. stereo = 1) - int dyn_range_ctrl:8; // dynamic range control (0x80 if off) - }; +int chan_count:3; // number of audio channels - 1 (e.g. stereo = 1) +int dyn_range_ctrl:8; // dynamic range control (0x80 if off) +}; */ struct LPCMFrame { - unsigned char PES[HDR_SIZE]; - unsigned char LPCM[LPCM_SIZE]; - unsigned char Data[MAX_FRAMESIZE-HDR_SIZE-LPCM_SIZE]; + unsigned char PES[HDR_SIZE]; + unsigned char LPCM[LPCM_SIZE]; + unsigned char Data[MAX_FRAMESIZE - HDR_SIZE - LPCM_SIZE]; }; #include "vdr_sound.c" - // --- mgPCMPlayer ---------------------------------------------------------- /*! @@ -113,1546 +110,1657 @@ struct LPCMFrame * VDR as a player and inherits from cThread in order to implement a separate thread * for the decoding process. */ -class mgPCMPlayer : public cPlayer, cThread +class mgPCMPlayer:public cPlayer, cThread { -private: + private: - //! \brief indicates, whether the player is currently active - bool m_active; +//! \brief indicates, whether the player is currently active + bool m_active; - //! \brief indicates, whether the player has been started - bool m_started; +//! \brief indicates, whether the player has been started + bool m_started; - //! \brief a buffer for decoded sound - cRingBufferFrame *m_ringbuffer; +//! \brief a buffer for decoded sound + cRingBufferFrame *m_ringbuffer; - //! \brief a mutex for the playmode - cMutex m_playmode_mutex; +//! \brief a mutex for the playmode + cMutex m_playmode_mutex; - //! \brief a condition to signal playmode changes - cCondVar m_playmode_cond; +//! \brief a condition to signal playmode changes + cCondVar m_playmode_cond; - //! \brief the current playlist - mgPlaylist *m_playlist; +//! \brief the current playlist + mgSelection *m_playlist; - //! \brief the currently played or to be played item - mgContentItem *m_current; +//! \brief the currently played or to be played item + mgContentItem *m_current; - //! \brief the currently playing item - mgContentItem *m_playing; - - //! \brief the decoder responsible for the currently playing item - mgDecoder *m_decoder; +//! \brief the currently playing item + mgContentItem *m_playing; - //! \brief the index where to start the playlist at - int m_first; +//! \brief the decoder responsible for the currently playing item + mgDecoder *m_decoder; - cFrame *m_rframe, *m_pframe; + cFrame *m_rframe, *m_pframe; - enum ePlayMode - { - pmPlay, - pmStopped, - pmPaused, - pmStartup - }; - ePlayMode m_playmode; - - enum eState - { - msStart, msStop, - msDecode, msNormalize, - msResample, msOutput, - msError, msEof, msWait - }; - eState m_state; + enum ePlayMode + { + pmPlay, + pmStopped, + pmPaused, + pmStartup + }; + ePlayMode m_playmode; + + enum eState + { + msStart, msStop, + msDecode, msNormalize, + msResample, msOutput, + msError, msEof, msWait + }; + eState m_state; - bool levelgood; - unsigned int dvbSampleRate; + bool levelgood; + unsigned int dvbSampleRate; - // - int m_index; +// + int m_index; - void Empty(); - bool NextFile( ); - bool PrevFile(); - void StopPlay(); + void Empty (); + bool SkipFile (int step=1); + void PlayTrack(); + void StopPlay (); - void SetPlayMode(ePlayMode mode); - void WaitPlayMode(ePlayMode mode, bool inv); + void SetPlayMode (ePlayMode mode); + void WaitPlayMode (ePlayMode mode, bool inv); -protected: - virtual void Activate(bool On); - virtual void Action(void); + protected: + virtual void Activate (bool On); + virtual void Action (void); -public: - mgPCMPlayer(mgPlaylist *plist, unsigned first); - virtual ~mgPCMPlayer(); + public: + mgPCMPlayer (mgSelection * plist); + virtual ~ mgPCMPlayer (); - bool Active() { return m_active; } + bool Active () + { + return m_active; + } - void Pause(); - void Play(); - void Forward(); - void Backward(); + void Pause (); + void Play (); + void Forward (); + void Backward (); - void Goto(int Index, bool Still=false); - void SkipSeconds(int secs); - void ToggleShuffle(void); - void ToggleLoop(void); + void Goto (int Index, bool Still = false); + void SkipSeconds (int secs); + void ToggleShuffle (void); + void ToggleLoop (void); - virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame=false); - // bool GetPlayInfo(cMP3PlayInfo *rm); // LVW + virtual bool GetIndex (int &Current, int &Total, bool SnapToIFrame = false); +// bool GetPlayInfo(cMP3PlayInfo *rm); // LVW - void NewPlaylist( mgPlaylist *plist, unsigned first ); - mgContentItem *getCurrent () { return m_current; } - mgPlaylist *getPlaylist () { return m_playlist; } + void ReloadPlaylist (); + void NewPlaylist (mgSelection * plist); + mgContentItem *getCurrent () + { + return m_current; + } + mgSelection *getPlaylist () + { + return m_playlist; + } }; -mgPCMPlayer::mgPCMPlayer(mgPlaylist *plist, unsigned first) - : cPlayer( the_setup.BackgrMode? pmAudioOnly: pmAudioOnlyBlack ), - m_first( first ) +mgPCMPlayer::mgPCMPlayer (mgSelection * plist):cPlayer (the_setup. +BackgrMode ? pmAudioOnly : +pmAudioOnlyBlack) { - m_playlist = plist; + m_playlist = plist; - m_active = true; - m_started = false; + m_active = true; + m_started = false; - m_ringbuffer = new cRingBufferFrame( MP3BUFSIZE ); + m_ringbuffer = new cRingBufferFrame (MP3BUFSIZE); - m_rframe = 0; - m_pframe = 0; - m_decoder = 0; + m_rframe = 0; + m_pframe = 0; + m_decoder = 0; - m_playmode = pmStartup; - m_state = msStop; + m_playmode = pmStartup; + m_state = msStop; - m_index = 0; - m_playing = 0; - m_current = 0; + m_index = 0; + m_playing = 0; + m_current = 0; } -mgPCMPlayer::~mgPCMPlayer() -{ - Detach(); - delete m_ringbuffer; +mgPCMPlayer::~mgPCMPlayer () +{ + Detach (); + delete m_playlist; + delete m_ringbuffer; } -void mgPCMPlayer::Activate(bool on) +void +mgPCMPlayer::PlayTrack() { - MGLOG( "mgPCMPlayer::Activate" ); - if( on ) + mgContentItem * newcurr = m_playlist->getCurrentTrack (); + if (newcurr) { - if( m_playlist && !m_started ) - { - m_playmode = pmStartup; - Start(); - - m_started = true; - m_current = 0; + if (m_current) delete m_current; + m_current = new mgContentItem(newcurr); + } + Play (); +} - m_playmode_mutex.Lock(); - WaitPlayMode( pmStartup, true ); // wait for the decoder to become ready - m_playmode_mutex.Unlock(); +void +mgPCMPlayer::Activate (bool on) +{ + MGLOG ("mgPCMPlayer::Activate"); + if (on) + { + if (m_playlist && !m_started) + { + m_playmode = pmStartup; + Start (); - Lock(); + m_started = true; + if (m_current) delete m_current; + m_current = 0; - m_playlist->initialize( ); - if( m_first > 0 ) - { - m_playlist->gotoPosition( m_first ); - } - m_current = m_playlist->getCurrent(); - Play(); + m_playmode_mutex.Lock (); + WaitPlayMode (pmStartup, true); // wait for the decoder to become ready + m_playmode_mutex.Unlock (); - Unlock(); - } + Lock (); + PlayTrack(); + Unlock (); + } } - else if( m_started && m_active ) + else if (m_started && m_active) { - Lock(); - StopPlay(); - Unlock(); + Lock (); + StopPlay (); + Unlock (); - m_active = false; - SetPlayMode( pmStartup ); + m_active = false; + SetPlayMode (pmStartup); - Cancel(2); + Cancel (2); } } -void mgPCMPlayer::NewPlaylist( mgPlaylist *plist, unsigned start ) +void +mgPCMPlayer::ReloadPlaylist() { - MGLOG( "mgPCMPlayer::NewPlaylist" ); + Lock (); + m_playlist->clearCache(); + Unlock (); +} - Lock(); - StopPlay(); - Unlock(); +void +mgPCMPlayer::NewPlaylist (mgSelection * plist) +{ + MGLOG ("mgPCMPlayer::NewPlaylist"); - // memory management of playlists should happen elsewhere (menu, content) - m_playlist = plist; - m_current = 0; + Lock (); + StopPlay (); - if( start > 0 ) - { - m_playlist->gotoPosition( (unsigned) start ); - m_current = m_playlist->getCurrent(); - Play(); - } - else if( NextFile() ) - { - Play(); - } + delete m_playlist; + m_playlist = plist; + PlayTrack(); + Unlock (); } -void mgPCMPlayer::SetPlayMode(ePlayMode mode) +void +mgPCMPlayer::SetPlayMode (ePlayMode mode) { - m_playmode_mutex.Lock(); - if( mode != m_playmode ) + m_playmode_mutex.Lock (); + if (mode != m_playmode) { - m_playmode = mode; - m_playmode_cond.Broadcast(); + m_playmode = mode; + m_playmode_cond.Broadcast (); } - m_playmode_mutex.Unlock(); + m_playmode_mutex.Unlock (); } -void mgPCMPlayer::WaitPlayMode(ePlayMode mode, bool inv) + +void +mgPCMPlayer::WaitPlayMode (ePlayMode mode, bool inv) { - // must be called with m_playmode_mutex LOCKED !!! +// must be called with m_playmode_mutex LOCKED !!! - while( m_active && ( (!inv && mode != m_playmode) || (inv && mode == m_playmode) ) ) + while (m_active + && ((!inv && mode != m_playmode) || (inv && mode == m_playmode))) { - m_playmode_cond.Wait(m_playmode_mutex); + m_playmode_cond.Wait (m_playmode_mutex); } } -void mgPCMPlayer::Action(void) -{ - MGLOG( "mgPCMPlayer::Action" ); - struct mgDecode *ds=0; - struct mad_pcm *pcm=0; - cResample resample[2]; - unsigned int nsamples[2]; - const mad_fixed_t *data[2]; - cScale scale; - cLevel level; - cNormalize norm; - bool haslevel=false; - struct LPCMFrame lpcmFrame; - const unsigned char *p=0; - int pc = 0, only48khz = the_setup.Only48kHz; - cPoller poll; +void +mgPCMPlayer::Action (void) +{ + MGLOG ("mgPCMPlayer::Action"); + + struct mgDecode *ds = 0; + struct mad_pcm *pcm = 0; + cResample resample[2]; + unsigned int nsamples[2]; + const mad_fixed_t *data[2]; + cScale scale; + cLevel level; + cNormalize norm; + bool haslevel = false; + struct LPCMFrame lpcmFrame; + const unsigned char *p = 0; + int pc = 0, only48khz = the_setup.Only48kHz; + cPoller poll; #ifdef DEBUG - int beat=0; + int beat = 0; #endif - dsyslog( "muggle: player thread started (pid=%d)", getpid() ); - - memset( &lpcmFrame, 0, sizeof(lpcmFrame) ); - lpcmFrame.PES[2]=0x01; - lpcmFrame.PES[3]=0xbd; - lpcmFrame.PES[6]=0x87; - lpcmFrame.LPCM[0]=0xa0; // substream ID - lpcmFrame.LPCM[1]=0xff; - lpcmFrame.LPCM[5]=0x01; - lpcmFrame.LPCM[6]=0x80; - - dvbSampleRate = 48000; - m_state = msStop; - SetPlayMode( pmStopped ); - - while( m_active ) + dsyslog ("muggle: player thread started (pid=%d)", getpid ()); + + memset (&lpcmFrame, 0, sizeof (lpcmFrame)); + lpcmFrame.PES[2] = 0x01; + lpcmFrame.PES[3] = 0xbd; + lpcmFrame.PES[6] = 0x87; + lpcmFrame.LPCM[0] = 0xa0; // substream ID + lpcmFrame.LPCM[1] = 0xff; + lpcmFrame.LPCM[5] = 0x01; + lpcmFrame.LPCM[6] = 0x80; + + dvbSampleRate = 48000; + m_state = msStop; + SetPlayMode (pmStopped); + + while (m_active) { #ifdef DEBUG - if(time(0)>=beat+30) - { - std::cout << "mgPCMPlayer::Action: heartbeat buffer=" << m_ringbuffer->Available() << std::endl << std::flush; - scale.Stats(); if(haslevel) norm.Stats(); - beat=time(0); - } + if (time (0) >= beat + 30) + { + std:: + cout << "mgPCMPlayer::Action: heartbeat buffer=" << m_ringbuffer-> + Available () << std::endl << std::flush; + scale.Stats (); + if (haslevel) + norm.Stats (); + beat = time (0); + } #endif - Lock(); - - if( !m_rframe && m_playmode == pmPlay ) - { - switch( m_state ) - { - case msStart: - { - m_index = 0; - m_playing = m_current; - - if( m_playing && m_playing != &(mgContentItem::UNDEFINED) ) - { - std::string filename = m_playing->getSourceFile(); - // mgDebug( 1, "mgPCMPlayer::Action: music file is %s", filename.c_str() ); - - if( ( m_decoder = mgDecoders::findDecoder( m_playing ) ) && m_decoder->start() ) - { - levelgood = true; - haslevel = false; - - scale.Init(); - level.Init(); - - m_state = msDecode; - - break; - } - } - m_state = msEof; - } break; - case msDecode: - { - ds = m_decoder->decode(); - switch( ds->status ) - { - case dsPlay: - { - pcm = ds->pcm; - m_index = ds->index/1000; - m_state = msNormalize; - } break; - case dsSkip: - case dsSoftError: - { - // skipping, state unchanged, next decode - } break; - case dsEof: - { - m_state = msEof; - } break; - case dsOK: - case dsError: - { - m_state = msError; - } break; - } - } break; - case msNormalize: - { - if(!haslevel) - { - if( levelgood ) - { - level.GetPower( pcm ); - } - } - else - { - norm.AddGain( pcm ); - } - m_state = msResample; - } break; - case msResample: - { + Lock (); + + if (!m_rframe && m_playmode == pmPlay) + { + switch (m_state) + { + case msStart: + { + m_index = 0; + m_playing = m_current; + + if (m_playing) + { + std::string filename = m_playing->getSourceFile (); +mgDebug( 1, "mgPCMPlayer::Action: music file is %s", filename.c_str() ); + + if ((m_decoder = mgDecoders::findDecoder (m_playing)) + && m_decoder->start ()) + { + levelgood = true; + haslevel = false; + + scale.Init (); + level.Init (); + + m_state = msDecode; + + break; + } + } + m_state = msEof; + } + break; + case msDecode: + { + ds = m_decoder->decode (); + switch (ds->status) + { + case dsPlay: + { + pcm = ds->pcm; + m_index = ds->index / 1000; + m_state = msNormalize; + } + break; + case dsSkip: + case dsSoftError: + { +// skipping, state unchanged, next decode + } + break; + case dsEof: + { + m_state = msEof; + } + break; + case dsOK: + case dsError: + { + m_state = msError; + } + break; + } + } + break; + case msNormalize: + { + if (!haslevel) + { + if (levelgood) + { + level.GetPower (pcm); + } + } + else + { + norm.AddGain (pcm); + } + m_state = msResample; + } + break; + case msResample: + { #ifdef DEBUG - { - static unsigned int oldrate=0; - if(oldrate!=pcm->samplerate) - { - std::cout << "mgPCMPlayer::Action: new input sample rate " << pcm->samplerate << std::endl << std::flush; - oldrate = pcm->samplerate; - } - } + { + static unsigned int oldrate = 0; + if (oldrate != pcm->samplerate) + { + std:: + cout << "mgPCMPlayer::Action: new input sample rate " + << pcm->samplerate << std::endl << std::flush; + oldrate = pcm->samplerate; + } + } #endif - nsamples[0] = nsamples[1] = pcm->length; - data[0] = pcm->samples[0]; - data[1] = pcm->channels > 1 ? pcm->samples[1]: 0; - - lpcmFrame.LPCM[5]&=0xcf; - dvbSampleRate=48000; - if(!only48khz) - { - switch(pcm->samplerate) - { // If one of the supported frequencies, do it without resampling. - case 96000: - { // Select a "even" upsampling frequency if possible, too. - lpcmFrame.LPCM[5] |= 1 << 4; - dvbSampleRate = 96000; - } break; - - //case 48000: // this is already the default ... - // lpcmFrame.LPCM[5]|=0<<4; - // dvbSampleRate=48000; - // break; - case 11025: - case 22050: - case 44100: - { - lpcmFrame.LPCM[5]|=2<<4; - dvbSampleRate = 44100; - } break; - case 8000: - case 16000: - case 32000: - { - lpcmFrame.LPCM[5]|=3<<4; - dvbSampleRate = 32000; - } break; - } - } - - if( dvbSampleRate != pcm->samplerate ) - { - if( resample[0].SetInputRate( pcm->samplerate, dvbSampleRate ) ) - { - nsamples[0] = resample[0].ResampleBlock( nsamples[0], data[0] ); - data[0] = resample[0].Resampled(); - } - if(data[1] && resample[1].SetInputRate( pcm->samplerate, dvbSampleRate ) ) - { - nsamples[1] = resample[1].ResampleBlock( nsamples[1], data[1] ); - data[1] = resample[1].Resampled(); - } - } - m_state=msOutput; - } break; - case msOutput: - { - if( nsamples[0] > 0 ) - { - unsigned int outlen = scale.ScaleBlock( lpcmFrame.Data, - sizeof(lpcmFrame.Data), - nsamples[0], data[0], - data[1], - the_setup.AudioMode? amDither: amRound ); - if( outlen ) - { - outlen += sizeof(lpcmFrame.LPCM)+LEN_CORR; - lpcmFrame.PES[4] = outlen >> 8; - lpcmFrame.PES[5] = outlen; - m_rframe = new cFrame( (unsigned char *)&lpcmFrame, - outlen + sizeof( lpcmFrame.PES ) - LEN_CORR ); - } - } - else - { - m_state=msDecode; - } - } break; - case msError: - case msEof: - { - if( NextFile() ) - { - m_state = msStart; - } - else - { - m_state = msWait; - } - } // fall through - case msStop: - { - m_playing = 0; - if( m_decoder ) - { // who deletes decoder? - m_decoder->stop(); - m_decoder = 0; - } - - levelgood = false; - - scale.Stats(); - if( haslevel ) - { - norm.Stats(); - } - if( m_state == msStop ) - { // might be unequal in case of fall through from eof/error - SetPlayMode( pmStopped ); - } - } break; - case msWait: - { - if( m_ringbuffer->Available() == 0 ) - { - m_active = false; - SetPlayMode(pmStopped); - } - } break; - } - } - - if( m_rframe && m_ringbuffer->Put( m_rframe ) ) - { - m_rframe = 0; - } - - if( !m_pframe && m_playmode == pmPlay ) - { - m_pframe = m_ringbuffer->Get(); - if( m_pframe ) - { - p = m_pframe->Data(); - pc = m_pframe->Count(); - } - } - - if( m_pframe ) - { - int w = PlayVideo( p, pc ); - if( w > 0 ) - { - p += w; - pc -= w; - - if( pc <= 0 ) - { - m_ringbuffer->Drop(m_pframe); - m_pframe=0; - } - } - else if( w < 0 && FATALERRNO ) - { - LOG_ERROR; - break; - } - } - - Unlock(); - - if( (m_rframe || m_state == msWait) && m_pframe ) - { - // Wait for output to become ready - DevicePoll( poll, 500 ); - } - else - { - if( m_playmode != pmPlay ) - { - m_playmode_mutex.Lock(); - - if( m_playmode != pmPlay ) - { - WaitPlayMode( m_playmode, true ); // Wait on playMode change - } - m_playmode_mutex.Unlock(); - } - } + nsamples[0] = nsamples[1] = pcm->length; + data[0] = pcm->samples[0]; + data[1] = pcm->channels > 1 ? pcm->samples[1] : 0; + + lpcmFrame.LPCM[5] &= 0xcf; + dvbSampleRate = 48000; + if (!only48khz) + { + switch (pcm->samplerate) + { // If one of the supported frequencies, do it without resampling. + case 96000: + { // Select a "even" upsampling frequency if possible, too. + lpcmFrame.LPCM[5] |= 1 << 4; + dvbSampleRate = 96000; + } + break; + +//case 48000: // this is already the default ... +// lpcmFrame.LPCM[5]|=0<<4; +// dvbSampleRate=48000; +// break; + case 11025: + case 22050: + case 44100: + { + lpcmFrame.LPCM[5] |= 2 << 4; + dvbSampleRate = 44100; + } + break; + case 8000: + case 16000: + case 32000: + { + lpcmFrame.LPCM[5] |= 3 << 4; + dvbSampleRate = 32000; + } + break; + } + } + + if (dvbSampleRate != pcm->samplerate) + { + if (resample[0]. + SetInputRate (pcm->samplerate, dvbSampleRate)) + { + nsamples[0] = + resample[0].ResampleBlock (nsamples[0], data[0]); + data[0] = resample[0].Resampled (); + } + if (data[1] + && resample[1].SetInputRate (pcm->samplerate, + dvbSampleRate)) + { + nsamples[1] = + resample[1].ResampleBlock (nsamples[1], data[1]); + data[1] = resample[1].Resampled (); + } + } + m_state = msOutput; + } + break; + case msOutput: + { + if (nsamples[0] > 0) + { + unsigned int outlen = scale.ScaleBlock (lpcmFrame.Data, + sizeof (lpcmFrame. + Data), + nsamples[0], + data[0], + data[1], + the_setup. + AudioMode ? + amDither : + amRound); + if (outlen) + { + outlen += sizeof (lpcmFrame.LPCM) + LEN_CORR; + lpcmFrame.PES[4] = outlen >> 8; + lpcmFrame.PES[5] = outlen; + m_rframe = new cFrame ((unsigned char *) &lpcmFrame, + outlen + + sizeof (lpcmFrame.PES) - + LEN_CORR); + } + } + else + { + m_state = msDecode; + } + } + break; + case msError: + case msEof: + { + if (SkipFile ()) + { + m_state = msStart; + } + else + { + m_state = msWait; + } + } // fall through + case msStop: + { + m_playing = 0; + if (m_decoder) + { // who deletes decoder? + m_decoder->stop (); + m_decoder = 0; + } + + levelgood = false; + + scale.Stats (); + if (haslevel) + { + norm.Stats (); + } + if (m_state == msStop) + { // might be unequal in case of fall through from eof/error + SetPlayMode (pmStopped); + } + } + break; + case msWait: + { + if (m_ringbuffer->Available () == 0) + { + m_active = false; + SetPlayMode (pmStopped); + } + } + break; + } + } + + if (m_rframe && m_ringbuffer->Put (m_rframe)) + { + m_rframe = 0; + } + + if (!m_pframe && m_playmode == pmPlay) + { + m_pframe = m_ringbuffer->Get (); + if (m_pframe) + { + p = m_pframe->Data (); + pc = m_pframe->Count (); + } + } + + if (m_pframe) + { + int w = PlayVideo (p, pc); + if (w > 0) + { + p += w; + pc -= w; + + if (pc <= 0) + { + m_ringbuffer->Drop (m_pframe); + m_pframe = 0; + } + } + else if (w < 0 && FATALERRNO) + { + LOG_ERROR; + break; + } + } + + Unlock (); + + if ((m_rframe || m_state == msWait) && m_pframe) + { +// Wait for output to become ready + DevicePoll (poll, 500); + } + else + { + if (m_playmode != pmPlay) + { + m_playmode_mutex.Lock (); + + if (m_playmode != pmPlay) + { + // Wait on playMode change + WaitPlayMode (m_playmode, true); + } + m_playmode_mutex.Unlock (); + } + } } - - Lock(); - if( m_rframe ) + Lock (); + + if (m_rframe) { - delete m_rframe; - m_rframe=0; + delete m_rframe; + m_rframe = 0; } - if( m_decoder ) - { // who deletes decoder? - m_decoder->stop(); - m_decoder = 0; + if (m_decoder) + { // who deletes decoder? + m_decoder->stop (); + m_decoder = 0; } - - m_playing = 0; - SetPlayMode(pmStopped); + m_playing = 0; - Unlock(); + SetPlayMode (pmStopped); - m_active = false; - - dsyslog( "muggle: player thread ended (pid=%d)", getpid() ); + Unlock (); + + m_active = false; + + dsyslog ("muggle: player thread ended (pid=%d)", getpid ()); } -void mgPCMPlayer::Empty(void) + +void +mgPCMPlayer::Empty (void) { - MGLOG( "mgPCMPlayer::Empty" ); + MGLOG ("mgPCMPlayer::Empty"); - Lock(); + Lock (); - m_ringbuffer->Clear(); - DeviceClear(); + m_ringbuffer->Clear (); + DeviceClear (); - delete m_rframe; - m_rframe = 0; - m_pframe = 0; + delete m_rframe; + m_rframe = 0; + m_pframe = 0; - Unlock(); + Unlock (); } -void mgPCMPlayer::StopPlay() -{ // StopPlay() must be called in locked state!!! - MGLOG( "mgPCMPlayer::StopPlay" ); - if( m_playmode != pmStopped ) + +void +mgPCMPlayer::StopPlay () +{ // StopPlay() must be called in locked state!!! + MGLOG ("mgPCMPlayer::StopPlay"); + if (m_playmode != pmStopped) { - Empty(); - m_state = msStop; - SetPlayMode( pmPlay ); - Unlock(); // let the decode thread process the stop signal + Empty (); + m_state = msStop; + SetPlayMode (pmPlay); + Unlock (); // let the decode thread process the stop signal - m_playmode_mutex.Lock(); - WaitPlayMode( pmStopped, false ); - m_playmode_mutex.Unlock(); + m_playmode_mutex.Lock (); + WaitPlayMode (pmStopped, false); + m_playmode_mutex.Unlock (); - Lock(); + Lock (); } } -bool mgPCMPlayer::NextFile( ) -{ - mgContentItem *newcurr; - - bool res = false; - - if( m_playlist->skipFwd() ) - { - newcurr = m_playlist->getCurrent(); - } - else - { - newcurr = &(mgContentItem::UNDEFINED); - } - - if( newcurr && newcurr != &(mgContentItem::UNDEFINED) ) - { - m_current = newcurr; - mgMuggle::setResumeIndex( m_playlist->getIndex() ); - res = true; - } - - return res; -} -bool mgPCMPlayer::PrevFile(void) +bool mgPCMPlayer::SkipFile (int step) { - bool res = false; - - if( m_playlist->skipBack() ) + MGLOG("mgPCMPlayer::SkipFile"); + mgContentItem * newcurr = NULL; + if (m_playlist->skipTracks (step)) { - mgContentItem *newcurr = m_playlist->getCurrent(); - - if( newcurr && newcurr != &(mgContentItem::UNDEFINED) ) - { - m_current = newcurr; - mgMuggle::setResumeIndex( m_playlist->getIndex() ); - res = true; + newcurr = m_playlist->getCurrentTrack (); + if (newcurr) { + if (m_current) delete m_current; + m_current = new mgContentItem(newcurr); } } - - return res; + return (newcurr != NULL); } -void mgPCMPlayer::ToggleShuffle() +void +mgPCMPlayer::ToggleShuffle () { - m_playlist->toggleShuffleMode(); + m_playlist->toggleShuffleMode (); } -void mgPCMPlayer::ToggleLoop(void) + +void +mgPCMPlayer::ToggleLoop (void) { - m_playlist->toggleLoopMode(); + m_playlist->toggleLoopMode (); } -void mgPCMPlayer::Pause(void) + +void +mgPCMPlayer::Pause (void) { - if( m_playmode == pmPaused ) + if (m_playmode == pmPaused) { - Play(); + Play (); } - else + else { - if( m_playmode == pmPlay ) - { - // DeviceFreeze(); - SetPlayMode( pmPaused ); - } + if (m_playmode == pmPlay) + { +// DeviceFreeze(); + SetPlayMode (pmPaused); + } } } -void mgPCMPlayer::Play(void) + +void +mgPCMPlayer::Play (void) { - MGLOG( "mgPCMPlayer::Play" ); + MGLOG ("mgPCMPlayer::Play"); - Lock(); + Lock (); - if( m_playmode != pmPlay && m_current && m_current != &(mgContentItem::UNDEFINED) ) + if (m_playmode != pmPlay && m_current) { - if( m_playmode == pmStopped ) - { - m_state = msStart; - } - // DevicePlay(); // TODO? Commented out in original code, too - SetPlayMode( pmPlay ); + if (m_playmode == pmStopped) + { + m_state = msStart; + } +// DevicePlay(); // TODO? Commented out in original code, too + SetPlayMode (pmPlay); } - Unlock(); + Unlock (); } -void mgPCMPlayer::Forward() + +void +mgPCMPlayer::Forward () { - MGLOG( "mgPCMPlayer::Forward" ); + MGLOG ("mgPCMPlayer::Forward"); - Lock(); - if( NextFile() ) - { - StopPlay(); - Play(); + Lock (); + if (SkipFile ()) + { + StopPlay (); + Play (); } - Unlock(); + Unlock (); } -void mgPCMPlayer::Backward(void) + +void +mgPCMPlayer::Backward (void) { - Lock(); - if( PrevFile() ) - { - StopPlay(); - Play(); + MGLOG ("mgPCMPlayer::Backward"); + Lock (); + if (SkipFile (-1)) + { + StopPlay (); + Play (); } - Unlock(); + Unlock (); } -void mgPCMPlayer::Goto( int index, bool still ) + +void +mgPCMPlayer::Goto (int index, bool still) { - m_playlist->gotoPosition( index-1 ); - mgContentItem *next = m_playlist->getCurrent(); + m_playlist->setTrack (index - 1); + mgContentItem *next = m_playlist->getCurrentTrack (); - if( next && next != &(mgContentItem::UNDEFINED) ) //invalid + if (next) { - Lock(); - StopPlay(); - m_current = next; - Play(); - Unlock(); + Lock (); + StopPlay (); + if (m_current) delete m_current; + m_current = new mgContentItem(next); + Play (); + Unlock (); } } -void mgPCMPlayer::SkipSeconds(int secs) + +void +mgPCMPlayer::SkipSeconds (int secs) { - if( m_playmode != pmStopped ) + if (m_playmode != pmStopped) { - Lock(); - if( m_playmode == pmPaused ) - { - SetPlayMode( pmPlay ); - } - if( m_decoder && m_decoder->skip( secs, m_ringbuffer->Available(), dvbSampleRate ) ) - { - levelgood=false; - } - Empty(); - Unlock(); + Lock (); + if (m_playmode == pmPaused) + { + SetPlayMode (pmPlay); + } + if (m_decoder + && m_decoder->skip (secs, m_ringbuffer->Available (), + dvbSampleRate)) + { + levelgood = false; + } + Empty (); + Unlock (); } } -bool mgPCMPlayer::GetIndex( int ¤t, int &total, bool snaptoiframe ) + +bool mgPCMPlayer::GetIndex (int ¤t, int &total, bool snaptoiframe) { - if( m_current ) - { - current = SecondsToFrames( m_index ); - total = SecondsToFrames( m_current->getLength() ); - return true; + if (m_current) + { + current = SecondsToFrames (m_index); + total = SecondsToFrames (m_current->getDuration ()); + return true; } - return false; + return false; } + /* string mgPCMPlayer::CheckImage( string fileName, size_t j ) { - static char tmpFile[1024]; - char *tmp[2048]; - char *result = NULL; - FILE *fp; - - sprintf (tmpFile, "%s/%s", MP3Setup.ImageCacheDir, &fileName[j]); // ??? - strcpy (strrchr (tmpFile, '.'), ".mpg"); - d(printf("mp3[%d]: cache %s\n", getpid (), tmpFile)) - if ((fp = fopen (tmpFile, "rb"))) - { - fclose (fp); - result = tmpFile; - } - else - { - if ((fp = fopen (fileName, "rb"))) - { - fclose (fp); - d(printf("mp3[%d]: image %s found\n", getpid (), fileName)) - sprintf ((char *) tmp, "image_convert.sh \"%s\" \"%s\"", fileName, tmpFile); - system ((const char*) tmp); - result = tmpFile; - } - } - fp = fopen ("/tmp/vdr_mp3_current_image.txt", "w"); - fprintf (fp, "%s\n", fileName); - fclose (fp); - return result; +static char tmpFile[1024]; +char *tmp[2048]; +char *result = NULL; +FILE *fp; + +sprintf (tmpFile, "%s/%s", MP3Setup.ImageCacheDir, &fileName[j]); // ??? +strcpy (strrchr (tmpFile, '.'), ".mpg"); +d(printf("mp3[%d]: cache %s\n", getpid (), tmpFile)) +if ((fp = fopen (tmpFile, "rb"))) +{ +fclose (fp); +result = tmpFile; +} +else +{ +if ((fp = fopen (fileName, "rb"))) +{ +fclose (fp); +d(printf("mp3[%d]: image %s found\n", getpid (), fileName)) +sprintf ((char *) tmp, "image_convert.sh \"%s\" \"%s\"", fileName, tmpFile); +system ((const char*) tmp); +result = tmpFile; +} +} +fp = fopen ("/tmp/vdr_mp3_current_image.txt", "w"); +fprintf (fp, "%s\n", fileName); +fclose (fp); +return result; } char *cMP3Player::LoadImage(const char *fullname) { - size_t i, j = strlen (MP3Sources.GetSource()->BaseDir()) + 1; - char imageFile[1024]; - static char mpgFile[1024]; - char *p, *q = NULL; - char *imageSuffixes[] = { "png", "gif", "jpg" }; - - d(printf("mp3[%d]: checking %s for images\n", getpid (), fullname)) - strcpy (imageFile, fullname); - strcpy (mpgFile, ""); - // - // track specific image, e.g. <song>.jpg - // - p = strrchr (imageFile, '.'); - if (p) - { - for (i = 0; i < sizeof (imageSuffixes) / sizeof (imageSuffixes[0]); i++) - { - strcpy (p + 1, imageSuffixes[i]); - d(printf("mp3[%d]: %s\n", getpid (), imageFile)) - q = CheckImage (imageFile, j); - if (q) - { - strcpy (mpgFile, q); - } - } - } - // - // album specific image, e.g. cover.jpg in song directory - // - if (!strlen (mpgFile)) - { - p = strrchr (imageFile, '/'); - if (p) - { - strcpy (p + 1, "cover."); - p += 6; - for (i = 0; i < sizeof (imageSuffixes) / sizeof (imageSuffixes[0]); i++) - { - strcpy (p + 1, imageSuffixes[i]); - d(printf("mp3[%d]: %s\n", getpid (), imageFile)) - q = CheckImage (imageFile, j); - if (q) - { - strcpy (mpgFile, q); - } - } - } - } - // - // artist specific image, e.g. artist.jpg in artist directory - // - if (!strlen (mpgFile)) - { - p = strrchr (imageFile, '/'); - if (p) - { - *p = '\0'; - p = strrchr (imageFile, '/'); - } - if (p) - { - strcpy (p + 1, "artist."); - p += 7; - for (i = 0; i < sizeof (imageSuffixes) / sizeof (imageSuffixes[0]); i++) - { - strcpy (p + 1, imageSuffixes[i]); - d(printf("mp3[%d]: %s\n", getpid (), imageFile)) - q = CheckImage (imageFile, j); - if (q) - { - strcpy (mpgFile, q); - } - } - } - } - // - // default background image - // - if (!strlen (mpgFile)) - { - for (i = 0; i < sizeof (imageSuffixes) / sizeof (imageSuffixes[0]); i++) - { - sprintf (imageFile, "%s/background.%s", MP3Setup.ImageCacheDir, imageSuffixes[i]); - d(printf("mp3[%d]: %s\n", getpid (), imageFile)) - q = CheckImage (imageFile, strlen(MP3Setup.ImageCacheDir) + 1); - if (q) - { - strcpy (mpgFile, q); - } - } - } - if (!strlen (mpgFile)) - { - sprintf (mpgFile, "%s/background.mpg", MP3Setup.ImageCacheDir); - } - return mpgFile; +size_t i, j = strlen (MP3Sources.GetSource()->BaseDir()) + 1; +char imageFile[1024]; +static char mpgFile[1024]; +char *p, *q = NULL; +char *imageSuffixes[] = { "png", "gif", "jpg" }; + +d(printf("mp3[%d]: checking %s for images\n", getpid (), fullname)) +strcpy (imageFile, fullname); +strcpy (mpgFile, ""); +// +// track specific image, e.g. <song>.jpg +// +p = strrchr (imageFile, '.'); +if (p) +{ +for (i = 0; i < sizeof (imageSuffixes) / sizeof (imageSuffixes[0]); i++) +{ +strcpy (p + 1, imageSuffixes[i]); +d(printf("mp3[%d]: %s\n", getpid (), imageFile)) +q = CheckImage (imageFile, j); +if (q) +{ +strcpy (mpgFile, q); +} +} +} +// +// album specific image, e.g. cover.jpg in song directory +// +if (!strlen (mpgFile)) +{ +p = strrchr (imageFile, '/'); +if (p) +{ +strcpy (p + 1, "cover."); +p += 6; +for (i = 0; i < sizeof (imageSuffixes) / sizeof (imageSuffixes[0]); i++) +{ +strcpy (p + 1, imageSuffixes[i]); +d(printf("mp3[%d]: %s\n", getpid (), imageFile)) +q = CheckImage (imageFile, j); +if (q) +{ +strcpy (mpgFile, q); +} +} +} +} +// +// artist specific image, e.g. artist.jpg in artist directory +// +if (!strlen (mpgFile)) +{ +p = strrchr (imageFile, '/'); +if (p) +{ +*p = '\0'; +p = strrchr (imageFile, '/'); +} +if (p) +{ +strcpy (p + 1, "artist."); +p += 7; +for (i = 0; i < sizeof (imageSuffixes) / sizeof (imageSuffixes[0]); i++) +{ +strcpy (p + 1, imageSuffixes[i]); +d(printf("mp3[%d]: %s\n", getpid (), imageFile)) +q = CheckImage (imageFile, j); +if (q) +{ +strcpy (mpgFile, q); +} +} +} +} +// +// default background image +// +if (!strlen (mpgFile)) +{ +for (i = 0; i < sizeof (imageSuffixes) / sizeof (imageSuffixes[0]); i++) +{ +sprintf (imageFile, "%s/background.%s", MP3Setup.ImageCacheDir, imageSuffixes[i]); +d(printf("mp3[%d]: %s\n", getpid (), imageFile)) +q = CheckImage (imageFile, strlen(MP3Setup.ImageCacheDir) + 1); +if (q) +{ +strcpy (mpgFile, q); +} +} +} +if (!strlen (mpgFile)) +{ +sprintf (mpgFile, "%s/background.mpg", MP3Setup.ImageCacheDir); +} +return mpgFile; } void mgPCMPlayer::ShowImage (char *file) { - uchar *buffer; - int fd; - struct stat st; - struct video_still_picture sp; - - if ((fd = open (file, O_RDONLY)) >= 0) - { - d(printf("mp3[%d]: cover still picture %s\n", getpid (), file)) - fstat (fd, &st); - sp.iFrame = (char *) malloc (st.st_size); - if (sp.iFrame) - { - sp.size = st.st_size; - if (read (fd, sp.iFrame, sp.size) > 0) - { - buffer = (uchar *) sp.iFrame; - d(printf("mp3[%d]: new image frame (size %d)\n", getpid(), sp.size)) - if(MP3Setup.UseDeviceStillPicture) - DeviceStillPicture (buffer, sp.size); - else - { - for (int i = 1; i <= 25; i++) - { - send_pes_packet (buffer, sp.size, i); - } - } - } - free (sp.iFrame); - } - else - { - esyslog ("mp3[%d]: cannot allocate memory (%d bytes) for still image", - getpid(), (int) st.st_size); - } - close (fd); - } - else - { - esyslog ("mp3[%d]: cannot open image file '%s'", - getpid(), file); - } +uchar *buffer; +int fd; +struct stat st; +struct video_still_picture sp; + +if ((fd = open (file, O_RDONLY)) >= 0) +{ +d(printf("mp3[%d]: cover still picture %s\n", getpid (), file)) +fstat (fd, &st); +sp.iFrame = (char *) malloc (st.st_size); +if (sp.iFrame) +{ +sp.size = st.st_size; +if (read (fd, sp.iFrame, sp.size) > 0) +{ +buffer = (uchar *) sp.iFrame; +d(printf("mp3[%d]: new image frame (size %d)\n", getpid(), sp.size)) +if(MP3Setup.UseDeviceStillPicture) +DeviceStillPicture (buffer, sp.size); +else +{ +for (int i = 1; i <= 25; i++) +{ +send_pes_packet (buffer, sp.size, i); +} +} +} +free (sp.iFrame); +} +else +{ +esyslog ("mp3[%d]: cannot allocate memory (%d bytes) for still image", +getpid(), (int) st.st_size); +} +close (fd); +} +else +{ +esyslog ("mp3[%d]: cannot open image file '%s'", +getpid(), file); +} } void mgPCMPlayer::send_pes_packet(unsigned char *data, int len, int timestamp) { #define PES_MAX_SIZE 2048 - int ptslen = timestamp ? 5 : 1; - static unsigned char pes_header[PES_MAX_SIZE]; - - pes_header[0] = pes_header[1] = 0; - pes_header[2] = 1; - pes_header[3] = 0xe0; - - while(len > 0) - { - int payload_size = len; - if(6 + ptslen + payload_size > PES_MAX_SIZE) - payload_size = PES_MAX_SIZE - (6 + ptslen); - - pes_header[4] = (ptslen + payload_size) >> 8; - pes_header[5] = (ptslen + payload_size) & 255; - - if(ptslen == 5) - { - int x; - x = (0x02 << 4) | (((timestamp >> 30) & 0x07) << 1) | 1; - pes_header[8] = x; - x = ((((timestamp >> 15) & 0x7fff) << 1) | 1); - pes_header[7] = x >> 8; - pes_header[8] = x & 255; - x = ((((timestamp) & 0x7fff) < 1) | 1); - pes_header[9] = x >> 8; - pes_header[10] = x & 255; - } else - { - pes_header[6] = 0x0f; - } - - memcpy(&pes_header[6 + ptslen], data, payload_size); - PlayVideo(pes_header, 6 + ptslen + payload_size); - - len -= payload_size; - data += payload_size; - ptslen = 1; - } +int ptslen = timestamp ? 5 : 1; +static unsigned char pes_header[PES_MAX_SIZE]; + +pes_header[0] = pes_header[1] = 0; +pes_header[2] = 1; +pes_header[3] = 0xe0; + +while(len > 0) +{ +int payload_size = len; +if(6 + ptslen + payload_size > PES_MAX_SIZE) +payload_size = PES_MAX_SIZE - (6 + ptslen); + +pes_header[4] = (ptslen + payload_size) >> 8; +pes_header[5] = (ptslen + payload_size) & 255; + +if(ptslen == 5) +{ +int x; +x = (0x02 << 4) | (((timestamp >> 30) & 0x07) << 1) | 1; +pes_header[8] = x; +x = ((((timestamp >> 15) & 0x7fff) << 1) | 1); +pes_header[7] = x >> 8; +pes_header[8] = x & 255; +x = ((((timestamp) & 0x7fff) < 1) | 1); +pes_header[9] = x >> 8; +pes_header[10] = x & 255; +} else +{ +pes_header[6] = 0x0f; +} + +memcpy(&pes_header[6 + ptslen], data, payload_size); +PlayVideo(pes_header, 6 + ptslen + payload_size); + +len -= payload_size; +data += payload_size; +ptslen = 1; +} } */ // --- mgPlayerControl ------------------------------------------------------- -mgPlayerControl::mgPlayerControl( mgPlaylist *plist, unsigned start ) - : cControl( player = new mgPCMPlayer( plist, start ) ) +mgPlayerControl::mgPlayerControl (mgSelection * plist):cControl (player = +new +mgPCMPlayer (plist)) { - MGLOG( "mgPlayerControl::mgPlayerControl" ); - #if VDRVERSNUM >= 10307 - m_display = NULL; - m_menu = NULL; + m_display = NULL; + m_menu = NULL; #endif - m_has_osd = false; + m_visible = false; + m_has_osd = false; + m_track_view = true; + m_progress_view = true; - // obtain settings from last run - m_visible = the_setup.visible; - m_track_view = the_setup.trackview; - m_progress_view = the_setup.progressview; + m_szLastShowStatusMsg = NULL; - m_szLastShowStatusMsg = NULL; - - // Notify all cStatusMonitor - StatusMsgReplaying(); +// Notify all cStatusMonitor + StatusMsgReplaying (); } -mgPlayerControl::~mgPlayerControl() + +mgPlayerControl::~mgPlayerControl () { - // Notify cleanup all cStatusMonitor - cStatus::MsgReplaying(this, NULL); - if( m_szLastShowStatusMsg ) - { - free(m_szLastShowStatusMsg); - m_szLastShowStatusMsg = NULL; +// Stop(); +// Notify cleanup all cStatusMonitor + cStatus::MsgReplaying (this, NULL); + if (m_szLastShowStatusMsg) + { + free (m_szLastShowStatusMsg); + m_szLastShowStatusMsg = NULL; } - InternalHide(); - Stop(); + InternalHide (); + Stop (); } -bool mgPlayerControl::Active(void) + +bool mgPlayerControl::Active (void) { - return player && player->Active(); + return player && player->Active (); } -void mgPlayerControl::Stop(void) + +void +mgPlayerControl::Stop (void) { - if( player ) + if (player) { - delete player; - player = 0; + delete player; + player = 0; } } -void mgPlayerControl::Pause(void) + +void +mgPlayerControl::Pause (void) { - if( player ) + if (player) { - player->Pause(); + player->Pause (); } } -void mgPlayerControl::Play(void) + +void +mgPlayerControl::Play (void) { - if( player ) + if (player) { - player->Play(); + player->Play (); } } -void mgPlayerControl::Forward(void) + +void +mgPlayerControl::Forward (void) { - if( player ) + if (player) { - player->Forward(); + player->Forward (); } } -void mgPlayerControl::Backward(void) + +void +mgPlayerControl::Backward (void) { - if( player ) + if (player) { - player->Backward(); + player->Backward (); } } -void mgPlayerControl::SkipSeconds(int Seconds) + +void +mgPlayerControl::SkipSeconds (int Seconds) +{ + if (player) + { + player->SkipSeconds (Seconds); + } +} + + +void +mgPlayerControl::Goto (int Position, bool Still) { - if( player ) + if (player) { - player->SkipSeconds(Seconds); + player->Goto (Position, Still); } } -void mgPlayerControl::Goto(int Position, bool Still) + +void +mgPlayerControl::ToggleShuffle (void) { - if( player ) + if (player) { - player->Goto(Position, Still); + player->ToggleShuffle (); } } -void mgPlayerControl::ToggleShuffle(void) + +void +mgPlayerControl::ToggleLoop (void) { - if( player ) + if (player) { - player->ToggleShuffle(); + player->ToggleLoop (); } } -void mgPlayerControl::ToggleLoop(void) +void +mgPlayerControl::ReloadPlaylist () { - if( player ) + if (player) { - player->ToggleLoop(); + player->ReloadPlaylist (); } } -void mgPlayerControl::NewPlaylist(mgPlaylist *plist, unsigned start) +void +mgPlayerControl::NewPlaylist (mgSelection * plist) { - if( player ) + if (player) { - player->NewPlaylist(plist, start); + player->NewPlaylist (plist); } } -void mgPlayerControl::ShowContents() +void +mgPlayerControl::ShowContents () { #if VDRVERSNUM >= 10307 - if( !m_menu ) + if (!m_menu) { - m_menu = Skins.Current()->DisplayMenu(); + m_menu = Skins.Current ()->DisplayMenu (); } - if( player && m_menu ) + if (player && m_menu) { - int num_items = m_menu->MaxItems(); - - if( m_track_view ) - { - m_menu->Clear(); - m_menu->SetTitle( "Track info view" ); - - m_menu->SetTabs( 15 ); - - char *buf; - if( num_items > 0 ) - { - asprintf( &buf, "Title:\t%s", player->getCurrent()->getLabel(0).c_str() ); - m_menu->SetItem( buf, 0, false, false ); - free( buf ); - } - if( num_items > 1 ) - { - asprintf( &buf, "Artist:\t%s", player->getCurrent()->getLabel(1).c_str() ); - m_menu->SetItem( buf, 1, false, false ); - free( buf ); - } - if( num_items > 2 ) - { - asprintf( &buf, "Album:\t%s", player->getCurrent()->getLabel(2).c_str() ); - m_menu->SetItem( buf, 2, false, false ); - free( buf ); - } - if( num_items > 3 ) - { - asprintf( &buf, "Genre:\t%s", player->getCurrent()->getLabel(3).c_str() ); - m_menu->SetItem( buf, 3, false, false ); - free( buf ); - } - if( num_items > 4 ) - { - int len = player->getCurrent()->getLength(); - asprintf( &buf, "Length:\t%s", IndexToHMSF( SecondsToFrames( len ) ) ); - m_menu->SetItem( buf, 4, false, false ); - free( buf ); - } - if( num_items > 5 ) - { - asprintf( &buf, "Bit rate:\t%s", player->getCurrent()->getBitrate().c_str() ); - m_menu->SetItem( buf, 5, false, false ); - free( buf ); - } - if( num_items > 6 ) - { - int sr = player->getCurrent()->getSampleRate(); - - asprintf( &buf, "Sampling rate:\t%d", sr ); - m_menu->SetItem( buf, 6, false, false ); - free( buf ); - } - } - else - { - mgPlaylist *list = player->getPlaylist(); - if( list ) - { - // use items for playlist tag display - m_menu->Clear(); - m_menu->SetTitle( "Playlist info view" ); - - int cur = list->getIndex(); - - char *buf; - for( int i=0; i < num_items; i ++ ) - { - mgContentItem *item = list->getItem( cur-3+i ); - if( item->isValid() ) - { - asprintf( &buf, "%s\t%s", item->getLabel(0).c_str(), item->getLabel(1).c_str() ); - if( i < 3 ) - { // already played - m_menu->SetItem( buf, i, false, false ); - } - if( i > 3 ) - { // to be played - m_menu->SetItem( buf, i, false, true ); - } - if( i == 3 ) - { - m_menu->SetItem( buf, i, true, true ); - } - free( buf ); - } - } - } - } + int num_items = m_menu->MaxItems (); + + if (m_track_view) + { + m_menu->Clear (); + m_menu->SetTitle ("Track info view"); + + m_menu->SetTabs (15); + + char *buf; + if (num_items > 0) + { + asprintf (&buf, "Title:\t%s", + player->getCurrent ()->getTitle ().c_str ()); + m_menu->SetItem (buf, 0, false, false); + free (buf); + } + if (num_items > 1) + { + asprintf (&buf, "Artist:\t%s", + player->getCurrent ()->getArtist ().c_str ()); + m_menu->SetItem (buf, 1, false, false); + free (buf); + } + if (num_items > 2) + { + asprintf (&buf, "Album:\t%s", + player->getCurrent ()->getAlbum ().c_str ()); + m_menu->SetItem (buf, 2, false, false); + free (buf); + } + if (num_items > 3) + { + asprintf (&buf, "Genre:\t%s", + player->getCurrent ()->getGenre ().c_str ()); + m_menu->SetItem (buf, 3, false, false); + free (buf); + } + if (num_items > 4) + { + int len = player->getCurrent ()->getDuration (); + asprintf (&buf, "Length:\t%s", + IndexToHMSF (SecondsToFrames (len))); + m_menu->SetItem (buf, 4, false, false); + free (buf); + } + if (num_items > 5) + { + asprintf (&buf, "Bit rate:\t%s", + player->getCurrent ()->getBitrate ().c_str ()); + m_menu->SetItem (buf, 5, false, false); + free (buf); + } + if (num_items > 6) + { + int sr = player->getCurrent ()->getSampleRate (); + + asprintf (&buf, "Sampling rate:\t%d", sr); + m_menu->SetItem (buf, 6, false, false); + free (buf); + } + } + else + { + mgSelection *list = player->getPlaylist (); + if (list) + { +// use items for playlist tag display + m_menu->Clear (); + m_menu->SetTitle ("Now playing"); + m_menu->SetTabs (25); + + int cur = list->getTrackPosition (); + for (int i = 0; i < num_items; i++) + { + mgContentItem *item = list->getTrack (cur - 3 + i); + if (item) + { + char *buf; + asprintf (&buf, "%s\t%s", item->getTitle ().c_str (), + item->getArtist ().c_str ()); + m_menu->SetItem (buf, i, i == 3, i > 3); + free (buf); + } + } + } + } } #endif } -void mgPlayerControl::ShowProgress() + +void +mgPlayerControl::ShowProgress () { - if( player ) + if (player) { - char *buf; - bool play = true, forward = true; - int speed = -1; - - int current_frame, total_frames; - player->GetIndex( current_frame, total_frames ); - - if( !m_track_view ) - { // playlist stuff - mgPlaylist *list = player->getPlaylist(); - if( list ) - { - total_frames = SecondsToFrames( list->getLength() ); - current_frame += SecondsToFrames( list->getCompletedLength() ); - asprintf( &buf, "Playlist %s (%d/%d)", list->getListname().c_str(), list->getIndex()+1, list->getNumItems() ); - } - } - else - { // track view - asprintf( &buf, "%s: %s", player->getCurrent()->getLabel(1).c_str(), player->getCurrent()->getTitle().c_str() ); - } + char *buf; + bool play = true, forward = true; + int speed = -1; + + int current_frame, total_frames; + player->GetIndex (current_frame, total_frames); + + if (!m_track_view) + { // playlist stuff + mgSelection *list = player->getPlaylist (); + if (list) + { + list->clearCache(); // playlist can dynamically grow, force reload + total_frames = SecondsToFrames (list->getLength ()); + current_frame += SecondsToFrames (list->getCompletedLength ()); + asprintf (&buf, "%s (%d/%d)", list->getListname ().c_str (), + list->getTrackPosition () + 1, list->getNumTracks ()); + } + } + else + { // track view + asprintf (&buf, "%s: %s", + player->getCurrent ()->getArtist ().c_str (), + player->getCurrent ()->getTitle ().c_str ()); + } #if VDRVERSNUM >= 10307 - if( !m_display ) - { - m_display = Skins.Current()->DisplayReplay(false); - } - if( m_display ) - { - m_display->SetProgress( current_frame, total_frames ); - m_display->SetCurrent( IndexToHMSF( current_frame ) ); - m_display->SetTotal( IndexToHMSF( total_frames ) ); - m_display->SetTitle( buf ); - m_display->SetMode( play, forward, speed ); - m_display->Flush(); - } + if (!m_display) + { + m_display = Skins.Current ()->DisplayReplay (false); + } + if (m_display) + { + m_display->SetProgress (current_frame, total_frames); + m_display->SetCurrent (IndexToHMSF (current_frame)); + m_display->SetTotal (IndexToHMSF (total_frames)); + m_display->SetTitle (buf); + m_display->SetMode (play, forward, speed); + m_display->Flush (); + } #else - int w = Interface->Width(); - int h = Interface->Height(); - - Interface->WriteText( w/2, h/2, "Muggle is active!" ); - Interface->Flush(); + int w = Interface->Width (); + int h = Interface->Height (); + + Interface->WriteText (w / 2, h / 2, "Muggle is active!"); + Interface->Flush (); #endif - free( buf ); + free (buf); } } -void mgPlayerControl::Display() + +void +mgPlayerControl::Display () { - if( m_visible ) + if (m_visible) { - if( !m_has_osd ) - { - // open the osd if its not already there... + if (!m_has_osd) + { +// open the osd if its not already there... #if VDRVERSNUM >= 10307 #else - Interface->Open(); + Interface->Open (); #endif - m_has_osd = true; - } - - // now an osd is open, go on - if( m_progress_view ) - { + m_has_osd = true; + } + +// now an osd is open, go on + if (m_progress_view) + { #if VDRVERSNUM >= 10307 - if( m_menu ) - { - delete m_menu; - m_menu = NULL; - } + if (m_menu) + { + delete m_menu; + m_menu = NULL; + } #endif - ShowProgress(); - } - else - { + ShowProgress (); + } + else + { #if VDRVERSNUM >= 10307 - if( m_display ) - { - delete m_display; - m_display = NULL; - } + if (m_display) + { + delete m_display; + m_display = NULL; + } #endif - ShowContents(); - } + ShowContents (); + } } - else + else { - InternalHide(); + InternalHide (); } } -void mgPlayerControl::Hide() + +void +mgPlayerControl::Hide () { - m_visible = false; - - InternalHide(); + m_visible = false; + + InternalHide (); } -void mgPlayerControl::InternalHide() +void +mgPlayerControl::InternalHide () { - if( m_has_osd ) + if (m_has_osd) { #if VDRVERSNUM >= 10307 - if( m_display ) - { - delete m_display; - m_display = NULL; - } - if( m_menu ) - { - delete m_menu; - m_menu = NULL; - } + if (m_display) + { + delete m_display; + m_display = NULL; + } + if (m_menu) + { + delete m_menu; + m_menu = NULL; + } #else - Interface->Close(); + Interface->Close (); #endif - m_has_osd = false; + m_has_osd = false; } } -eOSState mgPlayerControl::ProcessKey(eKeys key) + +eOSState mgPlayerControl::ProcessKey (eKeys key) { - if( !Active() ) + if (key!=kNone) MGLOG ("mgPlayerControl::ProcessKey(eKeys key)"); + if (!Active ()) { - return osEnd; + return osEnd; } - StatusMsgReplaying(); + StatusMsgReplaying (); - Display(); + Display (); - eOSState state = cControl::ProcessKey(key); + eOSState + state = cControl::ProcessKey (key); - if( state == osUnknown ) + if (state == osUnknown) { - switch( key ) - { - case kUp: - { - Forward(); - } break; - case kDown: - { - Backward(); - } break; - case kRed: - { - if( !m_visible && player ) - { - mgPlaylist *pl = player->getPlaylist(); - - std::string s; - switch( pl->toggleLoopMode() ) - { - case mgPlaylist::LM_NONE: - { - s = tr( "Loop mode off" ); - } break; - case mgPlaylist::LM_SINGLE: - { - s = tr( "Loop mode single" ); - } break; - case mgPlaylist::LM_FULL: - { - s = tr( "Loop mode full" ); - } break; - default: - { - s = tr( "Unknown loop mode" ); - } - } + switch (key) + { + case kUp: + { + if (m_visible) + Backward(); + else + Forward (); + } + break; + case kDown: + { + if (m_visible) + Forward (); + else + Backward(); + } + break; + case kRed: + { + if (!m_visible && player) + { + mgSelection * + pl = player->getPlaylist (); + + std::string s; + switch (pl->toggleLoopMode ()) + { + case mgSelection::LM_NONE: + { + s = tr ("Loop mode off"); + } + break; + case mgSelection::LM_SINGLE: + { + s = tr ("Loop mode single"); + } + break; + case mgSelection::LM_FULL: + { + s = tr ("Loop mode full"); + } + break; + default: + { + s = tr ("Unknown loop mode"); + } + } #if VDRVERSNUM >= 10307 - Skins.Message(mtInfo, s.c_str() ); - Skins.Flush(); + Skins.Message (mtInfo, s.c_str ()); + Skins.Flush (); #else - Interface->Status( s.c_str() ); - Interface->Flush(); + Interface->Status (s.c_str ()); + Interface->Flush (); #endif - } - else - { - // toggle progress display between simple and detail - m_progress_view = !m_progress_view; - the_setup.progressview = m_progress_view; - Display(); - } - } break; - case kGreen: - { - if( !m_visible && player ) - { - mgPlaylist *pl = player->getPlaylist(); - - std::string s; - switch( pl->toggleShuffleMode() ) - { - case mgPlaylist::SM_NONE: - { - s = tr( "Shuffle mode off" ); - } break; - case mgPlaylist::SM_NORMAL: - { - s = tr( "Shuffle mode normal" ); - } break; - case mgPlaylist::SM_PARTY: - { - s = tr( "Shuffle mode party" ); - } break; - default: - { - s = tr( "Unknown shuffle mode" ); - } - } + } + else + { +// toggle progress display between simple and detail + m_progress_view = !m_progress_view; + Display (); + } + } + break; + case kGreen: + { + if (!m_visible && player) + { + mgSelection * + pl = player->getPlaylist (); + + std::string s; + switch (pl->toggleShuffleMode ()) + { + case mgSelection::SM_NONE: + { + s = tr ("Shuffle mode off"); + } + break; + case mgSelection::SM_NORMAL: + { + s = tr ("Shuffle mode normal"); + } + break; + case mgSelection::SM_PARTY: + { + s = tr ("Shuffle mode party"); + } + break; + default: + { + s = tr ("Unknown shuffle mode"); + } + } #if VDRVERSNUM >= 10307 - Skins.Message(mtInfo, s.c_str() ); - Skins.Flush(); + Skins.Message (mtInfo, s.c_str ()); + Skins.Flush (); #else - Interface->Status( s.c_str() ); - Interface->Flush(); + Interface->Status (s.c_str ()); + Interface->Flush (); #endif - } - else - { - // toggle progress display between playlist and track - m_track_view = !m_track_view; - the_setup.trackview = m_track_view ; - Display(); - } - } break; - case kPause: - case kYellow: - { - Pause(); - } break; - case kStop: - case kBlue: - { - InternalHide(); - Stop(); - - return osEnd; - } break; - case kOk: - { - m_visible = !m_visible; - the_setup.visible = m_visible; - Display(); - - return osContinue; - } break; - case kBack: - { - InternalHide(); - Stop(); - mgMuggle::setResumeIndex( 0 ); - - return osEnd; - } break; - default: - { - return osUnknown; - } - } + } + else + { +// toggle progress display between playlist and track + m_track_view = !m_track_view; + Display (); + } + } + break; + case kPause: + case kYellow: + { + Pause (); + } + break; + case kStop: + case kBlue: + { + InternalHide (); + Stop (); + + return osEnd; + } + break; + case kOk: + { + m_visible = !m_visible; + Display (); + + return osContinue; + } + break; + case kBack: + { + InternalHide (); + Stop (); + + return osEnd; + } + break; + default: + { + return osUnknown; + } + } } - return osContinue; + return osContinue; } -void mgPlayerControl::StatusMsgReplaying() + +void +mgPlayerControl::StatusMsgReplaying () { - char *szBuf=NULL; - if(player - && player->getCurrent() - && player->getPlaylist()) + MGLOG ("mgPlayerControl::StatusMsgReplaying()"); + char *szBuf = NULL; + if (player && player->getCurrent () && player->getPlaylist ()) { - char cLoopMode; - char cShuffle; - - switch( player->getPlaylist()->getLoopMode() ) - { - default: - case mgPlaylist::LM_NONE: - cLoopMode = '.'; // Loop mode off - break; - case mgPlaylist::LM_SINGLE: - cLoopMode = 'S'; // Loop mode single - break; - case mgPlaylist::LM_FULL: - cLoopMode = 'P'; // Loop mode fuel - break; - } - - switch( player->getPlaylist()->getShuffleMode() ) - { - default: - case mgPlaylist::SM_NONE: - cShuffle = '.'; // Shuffle mode off - break; - case mgPlaylist::SM_NORMAL: - cShuffle = 'S'; // Shuffle mode normal - break; - case mgPlaylist::SM_PARTY: - cShuffle = 'P'; // Shuffle mode party - break; + char cLoopMode; + char cShuffle; + + switch (player->getPlaylist ()->getLoopMode ()) + { + default: + case mgSelection::LM_NONE: + cLoopMode = '.'; // Loop mode off + break; + case mgSelection::LM_SINGLE: + cLoopMode = 'S'; // Loop mode single + break; + case mgSelection::LM_FULL: + cLoopMode = 'P'; // Loop mode fuel + break; } - if(player->getCurrent()->getLabel(1).length() > 0) - { - asprintf(&szBuf,"[%c%c] (%d/%d) %s - %s", - cLoopMode, - cShuffle, - player->getPlaylist()->getIndex() + 1,player->getPlaylist()->getNumItems(), - player->getCurrent()->getLabel(1).c_str(), - player->getCurrent()->getTitle().c_str()); - } - else - { - asprintf(&szBuf,"[%c%c] (%d/%d) %s", - cLoopMode, - cShuffle, - player->getPlaylist()->getIndex() + 1,player->getPlaylist()->getNumItems(), - player->getCurrent()->getTitle().c_str()); - } + switch (player->getPlaylist ()->getShuffleMode ()) + { + default: + case mgSelection::SM_NONE: + cShuffle = '.'; // Shuffle mode off + break; + case mgSelection::SM_NORMAL: + cShuffle = 'S'; // Shuffle mode normal + break; + case mgSelection::SM_PARTY: + cShuffle = 'P'; // Shuffle mode party + break; + } + + mgContentItem *tmp = player->getCurrent (); + if (tmp == NULL) + mgError("mgPlayerControl::StatusMsgReplaying: getCurrent() is NULL"); + if (tmp->getArtist ().length () > 0) + { + asprintf (&szBuf, "[%c%c] (%d/%d) %s - %s", + cLoopMode, + cShuffle, + player->getPlaylist ()->getTrackPosition () + 1, + player->getPlaylist ()->getNumTracks (), + player->getCurrent ()->getArtist ().c_str (), + player->getCurrent ()->getTitle ().c_str ()); + } + else + { + asprintf (&szBuf, "[%c%c] (%d/%d) %s", + cLoopMode, + cShuffle, + player->getPlaylist ()->getTrackPosition () + 1, + player->getPlaylist ()->getNumTracks (), + player->getCurrent ()->getTitle ().c_str ()); + } } - else + else { - asprintf(&szBuf,"[muggle]"); + asprintf (&szBuf, "[muggle]"); } - - //fprintf(stderr,"StatusMsgReplaying(%s)\n",szBuf); - if( szBuf ) - { - if( m_szLastShowStatusMsg == NULL - || 0 != strcmp(szBuf,m_szLastShowStatusMsg) ) - { - if(m_szLastShowStatusMsg) - { - free(m_szLastShowStatusMsg); - } - m_szLastShowStatusMsg = szBuf; - cStatus::MsgReplaying(this,m_szLastShowStatusMsg); - } - else - { - free(szBuf); - } + +//fprintf(stderr,"StatusMsgReplaying(%s)\n",szBuf); + if (szBuf) + { + if (m_szLastShowStatusMsg == NULL + || 0 != strcmp (szBuf, m_szLastShowStatusMsg)) + { + if (m_szLastShowStatusMsg) + { + free (m_szLastShowStatusMsg); + } + m_szLastShowStatusMsg = szBuf; + cStatus::MsgReplaying (this, m_szLastShowStatusMsg); + } + else + { + free (szBuf); + } } } - diff --git a/vdr_player.h b/vdr_player.h index e87d577..5b8ea9f 100644 --- a/vdr_player.h +++ b/vdr_player.h @@ -9,16 +9,16 @@ * * $Id$ * - * Adapted from + * Adapted from * MP3/MPlayer plugin to VDR (C++) * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> */ - #ifndef ___VDR_PLAYER_H #define ___VDR_PLAYER_H #include <player.h> +#include "mg_db.h" #if VDRVERSNUM >= 10307 class cOsd; #endif @@ -26,125 +26,124 @@ class cOsd; // ------------------------------------------------------------------- class mgPCMPlayer; -class mgPlaylist; // ------------------------------------------------------------------- -/*! +/*! * \brief exerts control over the player itself * * This control is launched from the main menu and manages a link * to the player. Key events are caught and signaled to the player. */ -class mgPlayerControl : public cControl +class mgPlayerControl:public cControl { -private: + private: - //! \brief the reference to the player - mgPCMPlayer *player; +//! \brief the reference to the player + mgPCMPlayer * player; - //! \brief indicates, whether the osd should be visible - int m_visible; +//! \brief indicates, whether the osd should be visible + bool m_visible; - //! \brief indicates, whether an osd is currently displayed - bool m_has_osd; +//! \brief indicates, whether an osd is currently displayed + bool m_has_osd; - //! \brief indicates, whether the osd displays a track view (true) or a playlist view (false) - int m_track_view; - - //! \brief indicates, whether the osd presents progress (true) or detail information (false) - int m_progress_view; + bool m_track_view; + bool m_progress_view; #if VDRVERSNUM >= 10307 - //! \brief a replay display to show the progress during playback - cSkinDisplayReplay *m_display; - cSkinDisplayMenu *m_menu; +//! \brief a replay display to show the progress during playback + cSkinDisplayReplay *m_display; + cSkinDisplayMenu *m_menu; - cOsd *osd; - const cFont *font; + cOsd *osd; + const cFont *font; #endif - //! \brief Last Message for Statusmonitor - char* m_szLastShowStatusMsg; +//! \brief Last Message for Statusmonitor + char *m_szLastShowStatusMsg; -public: + public: - /*! \brief construct a control with a playlist - * - * \param plist - the playlist to be played - * \param first - the index where to start the playlist - */ - mgPlayerControl(mgPlaylist *plist, unsigned first); +/*! \brief construct a control with a playlist + * + * \param plist - the playlist to be played + */ + mgPlayerControl (mgSelection * plist); - /*! \brief destructor - */ - virtual ~mgPlayerControl(); +/*! \brief destructor + */ + virtual ~ mgPlayerControl (); - //! \brief indicate, whether the corresponding player is active - bool Active(); +//! \brief indicate whether the corresponding player is active + bool Active (); - //! \brief stop the corresponding player - void Stop(); +//! \brief stop the corresponding player + void Stop (); - //! \brief toggle the pause mode of the corresponding player - void Pause(); +//! \brief toggle the pause mode of the corresponding player + void Pause (); - //! \brief start playing - void Play(); +//! \brief start playing + void Play (); - //! \brief skip to the next song - void Forward(); +//! \brief skip to the next song + void Forward (); - //! \brief skip to the previous song - void Backward(); +//! \brief skip to the previous song + void Backward (); - /*! \brief skip a specified number of seconds - * - * \param seconds - the number of seconds to skip - */ - void SkipSeconds(int seconds); +/*! \brief skip a specified number of seconds + * + * \param seconds - the number of seconds to skip + */ + void SkipSeconds (int seconds); - /*! \brief goto a certain position in the playlist - * - * \param index - the position in the playlist to skip to - * \param still - currently unused - */ - void Goto(int index, bool still = false); +/*! \brief goto a certain position in the playlist + * + * \param index - the position in the playlist to skip to + * \param still - currently unused + */ + void Goto (int index, bool still = false); - //! \brief toggle the shuffle mode of the corresponding player - void ToggleShuffle(); +//! \brief toggle the shuffle mode of the corresponding player + void ToggleShuffle (); - //! \brief toggle the loop mode of the corresponding player - void ToggleLoop(); +//! \brief toggle the loop mode of the corresponding player + void ToggleLoop (); - /*! \brief signal a new playlist - * - * The caller has to take care of deallocating the previous list - * - * \param plist - the new playlist to be played - * \param first - the index where to start the playlist - */ - void NewPlaylist( mgPlaylist *plist, unsigned start ); + /*! \brief tell the player to reload the play list. + * This is needed if we play a collection + * and the user changed the collection while playing it + */ + void ReloadPlaylist(); - //! \brief a progress display - void ShowProgress(); +/*! \brief signal a new playlist + * + * The caller has to take care of deallocating the previous list + * + * \param plist - the new playlist to be played + */ + void NewPlaylist (mgSelection * plist); - void Display(); +//! \brief a progress display + void ShowProgress (); - void ShowContents(); + void Display (); - //! \brief hide the osd, if present - void Hide(); + void ShowContents (); - //! \brief hide the osd, if present - void InternalHide(); +//! \brief hide the osd, if present + void Hide (); - //! \brief process key events - eOSState ProcessKey(eKeys key); +//! \brief hide the osd, if present + void InternalHide (); -protected: - //! \brief signal a played file to any cStatusMonitor inside vdr - void StatusMsgReplaying(); -}; +//! \brief process key events + eOSState ProcessKey (eKeys key); -#endif //___VDR_PLAYER_H + protected: +//! \brief signal a played file to any cStatusMonitor inside vdr + void StatusMsgReplaying (); +}; +#endif //___VDR_PLAYER_H diff --git a/vdr_setup.c b/vdr_setup.c index e6aa816..36a3b46 100644 --- a/vdr_setup.c +++ b/vdr_setup.c @@ -9,7 +9,7 @@ * * $Id$ * - * Partially adapted from + * Partially adapted from * MP3/MPlayer plugin to VDR (C++) * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> */ @@ -23,61 +23,66 @@ mgSetup the_setup; // --- mgMenuSetup ----------------------------------------------------------- -mgMenuSetup::mgMenuSetup() +mgMenuSetup::mgMenuSetup () { - static const char allowed[] = { "abcdefghijklmnopqrstuvwxyz0123456789-_" }; + m_data = the_setup; - m_data = the_setup; + SetSection (tr ("Muggle")); - SetSection( tr("Muggle") ); - - Add(new cMenuEditBoolItem(tr("Setup.Muggle$Initial loop mode"), &m_data.InitLoopMode)); - Add(new cMenuEditBoolItem(tr("Setup.Muggle$Initial shuffle mode"), &m_data.InitShuffleMode)); - Add(new cMenuEditBoolItem(tr("Setup.Muggle$Audio mode"), &m_data.AudioMode, tr("Round"), tr("Dither"))); - Add(new cMenuEditBoolItem(tr("Setup.Muggle$Use 48kHz mode only"), &m_data.Only48kHz)); - Add(new cMenuEditIntItem( tr("Setup.Muggle$Display mode"), &m_data.DisplayMode, 1, 3)); - Add(new cMenuEditBoolItem(tr("Setup.Muggle$Background mode"), &m_data.BackgrMode, tr("Black"), tr("Live"))); - Add(new cMenuEditIntItem( tr("Setup.Muggle$Normalizer level"), &m_data.TargetLevel, 0, MAX_TARGET_LEVEL)); - Add(new cMenuEditIntItem( tr("Setup.Muggle$Limiter level"), &m_data.LimiterLevel, MIN_LIMITER_LEVEL, 100)); - - Add(new cMenuEditBoolItem(tr("Setup.Muggle$Start replay with open display"), &m_data.visible )); - Add(new cMenuEditBoolItem(tr("Setup.Muggle$Start replay with progress display"), &m_data.progressview )); - Add(new cMenuEditBoolItem(tr("Setup.Muggle$Start replay with track display"), &m_data.trackview )); + Add (new + cMenuEditBoolItem (tr ("Setup.Muggle$Initial loop mode"), + &m_data.InitLoopMode)); + Add (new + cMenuEditBoolItem (tr ("Setup.Muggle$Initial shuffle mode"), + &m_data.InitShuffleMode)); + Add (new + cMenuEditBoolItem (tr ("Setup.Muggle$Audio mode"), &m_data.AudioMode, + tr ("Round"), tr ("Dither"))); + Add (new + cMenuEditBoolItem (tr ("Setup.Muggle$Use 48kHz mode only"), + &m_data.Only48kHz)); + Add (new + cMenuEditIntItem (tr ("Setup.Muggle$Display mode"), + &m_data.DisplayMode, 1, 3)); + Add (new + cMenuEditBoolItem (tr ("Setup.Muggle$Background mode"), + &m_data.BackgrMode, tr ("Black"), tr ("Live"))); + Add (new + cMenuEditIntItem (tr ("Setup.Muggle$Normalizer level"), + &m_data.TargetLevel, 0, MAX_TARGET_LEVEL)); + Add (new + cMenuEditIntItem (tr ("Setup.Muggle$Limiter level"), + &m_data.LimiterLevel, MIN_LIMITER_LEVEL, 100)); } -void mgMenuSetup::Store(void) -{ - the_setup = m_data; - SetupStore("InitLoopMode", the_setup.InitLoopMode ); - SetupStore("InitShuffleMode", the_setup.InitShuffleMode); - SetupStore("AudioMode", the_setup.AudioMode ); - SetupStore("DisplayMode", the_setup.DisplayMode ); - SetupStore("BackgrMode", the_setup.BackgrMode ); - SetupStore("TargetLevel", the_setup.TargetLevel ); - SetupStore("LimiterLevel", the_setup.LimiterLevel ); - SetupStore("Only48kHz", the_setup.Only48kHz ); +void +mgMenuSetup::Store (void) +{ + the_setup = m_data; - SetupStore("Visible", the_setup.visible ); - SetupStore("TrackView", the_setup.trackview ); - SetupStore("ProgressView", the_setup.progressview ); + SetupStore ("InitLoopMode", the_setup.InitLoopMode); + SetupStore ("InitShuffleMode", the_setup.InitShuffleMode); + SetupStore ("AudioMode", the_setup.AudioMode); + SetupStore ("DisplayMode", the_setup.DisplayMode); + SetupStore ("BackgrMode", the_setup.BackgrMode); + SetupStore ("TargetLevel", the_setup.TargetLevel); + SetupStore ("LimiterLevel", the_setup.LimiterLevel); + SetupStore ("Only48kHz", the_setup.Only48kHz); } + // --- mgSetup --------------------------------------------------------------- -mgSetup::mgSetup() +mgSetup::mgSetup () { - InitLoopMode = 0; - InitShuffleMode = 0; - AudioMode = 1; - DisplayMode = 3; - BackgrMode = 1; - TargetLevel = DEFAULT_TARGET_LEVEL; - LimiterLevel = DEFAULT_LIMITER_LEVEL; - Only48kHz = 0; - ToplevelDir = "/mnt/music/"; - - visible = 1; - trackview = 1; - progressview = 1; + InitLoopMode = 0; + InitShuffleMode = 0; + AudioMode = 1; + DisplayMode = 3; + BackgrMode = 1; + TargetLevel = DEFAULT_TARGET_LEVEL; + LimiterLevel = DEFAULT_LIMITER_LEVEL; + Only48kHz = 0; + ToplevelDir = "/mnt/music/"; } diff --git a/vdr_setup.h b/vdr_setup.h index e96df84..c21adb1 100644 --- a/vdr_setup.h +++ b/vdr_setup.h @@ -9,7 +9,7 @@ * * $Id$ * - * Adapted from + * Adapted from * MP3/MPlayer plugin to VDR (C++) * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> */ @@ -32,34 +32,30 @@ */ class mgSetup { -public: - int InitLoopMode; - int InitShuffleMode; - int AudioMode; - int DisplayMode; - int BackgrMode; - int MenuMode; - int TargetLevel; - int LimiterLevel; - int Only48kHz; + public: + int InitLoopMode; + int InitShuffleMode; + int AudioMode; + int DisplayMode; + int BackgrMode; + int MenuMode; + int TargetLevel; + int LimiterLevel; + int Only48kHz; - char *DbHost; - char *DbSocket; - char *DbName; - char *DbUser; - char *DbPass; - int DbPort; - bool GdCompatibility; - char *ToplevelDir; + char *DbHost; + char *DbSocket; + char *DbName; + char *DbUser; + char *DbPass; + int DbPort; + bool GdCompatibility; + char *ToplevelDir; - int visible; - int trackview; - int progressview; + char PathPrefix[MAX_STRING_LEN]; - char PathPrefix[MAX_STRING_LEN]; - - public: - mgSetup(void); + public: + mgSetup (void); }; extern mgSetup the_setup; @@ -67,15 +63,13 @@ extern mgSetup the_setup; /*! * \brief allow user to modify setup on OSD */ -class mgMenuSetup : public cMenuSetupPage +class mgMenuSetup:public cMenuSetupPage { -private: - mgSetup m_data; -protected: - virtual void Store(); -public: - mgMenuSetup(); + private: + mgSetup m_data; + protected: + virtual void Store (); + public: + mgMenuSetup (); }; - - -#endif //___SETUP_MP3_H +#endif //___SETUP_MP3_H diff --git a/vdr_sound.c b/vdr_sound.c index b06061c..7fc8361 100644 --- a/vdr_sound.c +++ b/vdr_sound.c @@ -3,13 +3,13 @@ * \brief Sound manipulation classes 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_sound.c,v 1.2 2004/05/28 15:29:19 lvw Exp $ + * $Id$ * - * Adapted from + * Adapted from * MP3/MPlayer plugin to VDR (C++) * (C) 2001,2002 Stefan Huelswitt <huels@iname.com> */ @@ -18,80 +18,97 @@ // The resample code has been adapted from the madplay project // (resample.c) found in the libmad distribution - + class cResample { -private: - mad_fixed_t ratio; - mad_fixed_t step; - mad_fixed_t last; - mad_fixed_t resampled[MAX_NSAMPLES]; -public: - bool SetInputRate(unsigned int oldrate, unsigned int newrate); - unsigned int ResampleBlock(unsigned int nsamples, const mad_fixed_t *old); - const mad_fixed_t *Resampled(void) { return resampled; } - }; - -bool cResample::SetInputRate(unsigned int oldrate, unsigned int newrate) + private: + mad_fixed_t ratio; + mad_fixed_t step; + mad_fixed_t last; + mad_fixed_t resampled[MAX_NSAMPLES]; + public: + bool SetInputRate (unsigned int oldrate, unsigned int newrate); + unsigned int ResampleBlock (unsigned int nsamples, const mad_fixed_t * old); + const mad_fixed_t *Resampled (void) + { + return resampled; + } +}; + +bool cResample::SetInputRate (unsigned int oldrate, unsigned int newrate) { - if(oldrate<8000 || oldrate>newrate*6) { // out of range - esyslog("WARNING: samplerate %d out of range 8000-%d\n",oldrate,newrate*6); - return 0; + if (oldrate < 8000 || oldrate > newrate * 6) + { // out of range + esyslog ("WARNING: samplerate %d out of range 8000-%d\n", oldrate, + newrate * 6); + return 0; } - ratio=mad_f_tofixed((double)oldrate/(double)newrate); - step=0; last=0; + ratio = mad_f_tofixed ((double) oldrate / (double) newrate); + step = 0; + last = 0; #ifdef DEBUG - static mad_fixed_t oldratio=0; - if(oldratio!=ratio) { - printf("mad: new resample ratio %f (from %d kHz to %d kHz)\n",mad_f_todouble(ratio),oldrate,newrate); - oldratio=ratio; + static mad_fixed_t + oldratio = 0; + if (oldratio != ratio) + { + printf ("mad: new resample ratio %f (from %d kHz to %d kHz)\n", + mad_f_todouble (ratio), oldrate, newrate); + oldratio = ratio; } #endif - return ratio!=MAD_F_ONE; + return ratio != MAD_F_ONE; } -unsigned int cResample::ResampleBlock(unsigned int nsamples, const mad_fixed_t *old) + +unsigned int +cResample::ResampleBlock (unsigned int nsamples, const mad_fixed_t * old) { - // This resampling algorithm is based on a linear interpolation, which is - // not at all the best sounding but is relatively fast and efficient. - // - // A better algorithm would be one that implements a bandlimited - // interpolation. - - mad_fixed_t *nsam=resampled; - const mad_fixed_t *end=old+nsamples; - const mad_fixed_t *begin=nsam; - - if(step < 0) { - step = mad_f_fracpart(-step); - - while (step < MAD_F_ONE) { - *nsam++ = step ? last+mad_f_mul(*old-last,step) : last; - step += ratio; - if(((step + 0x00000080L) & 0x0fffff00L) == 0) - step = (step + 0x00000080L) & ~0x0fffffffL; - } - step -= MAD_F_ONE; +// This resampling algorithm is based on a linear interpolation, which is +// not at all the best sounding but is relatively fast and efficient. +// +// A better algorithm would be one that implements a bandlimited +// interpolation. + + mad_fixed_t *nsam = resampled; + const mad_fixed_t *end = old + nsamples; + const mad_fixed_t *begin = nsam; + + if (step < 0) + { + step = mad_f_fracpart (-step); + + while (step < MAD_F_ONE) + { + *nsam++ = step ? last + mad_f_mul (*old - last, step) : last; + step += ratio; + if (((step + 0x00000080L) & 0x0fffff00L) == 0) + step = (step + 0x00000080L) & ~0x0fffffffL; + } + step -= MAD_F_ONE; } - while (end - old > 1 + mad_f_intpart(step)) { - old += mad_f_intpart(step); - step = mad_f_fracpart(step); - *nsam++ = step ? *old + mad_f_mul(old[1] - old[0], step) : *old; - step += ratio; - if (((step + 0x00000080L) & 0x0fffff00L) == 0) - step = (step + 0x00000080L) & ~0x0fffffffL; + while (end - old > 1 + mad_f_intpart (step)) + { + old += mad_f_intpart (step); + step = mad_f_fracpart (step); + *nsam++ = step ? *old + mad_f_mul (old[1] - old[0], step) : *old; + step += ratio; + if (((step + 0x00000080L) & 0x0fffff00L) == 0) + step = (step + 0x00000080L) & ~0x0fffffffL; } - if (end - old == 1 + mad_f_intpart(step)) { - last = end[-1]; - step = -step; + if (end - old == 1 + mad_f_intpart (step)) + { + last = end[-1]; + step = -step; } - else step -= mad_f_fromint(end - old); + else + step -= mad_f_fromint (end - old); - return nsam-begin; + return nsam - begin; } + // --- cLevel ---------------------------------------------------------------- // The normalize algorithm and parts of the code has been adapted from the @@ -123,201 +140,253 @@ unsigned int cResample::ResampleBlock(unsigned int nsamples, const mad_fixed_t * // We can then take the square root of the power to get maxi // mum sustained RMS amplitude. -class cLevel { -private: - double maxpow; - mad_fixed_t peak; - struct Power { - // smooth - int npow, wpow; - double powsum, pows[POW_WIN]; - // sum - unsigned int nsum; - double sum; - } power[2]; - // - inline void AddPower(struct Power *p, double pow); -public: - void Init(void); - void GetPower(struct mad_pcm *pcm); - double GetLevel(void); - double GetPeak(void); - }; - -void cLevel::Init(void) +class cLevel +{ + private: + double maxpow; + mad_fixed_t peak; + struct Power + { +// smooth + int npow, wpow; + double powsum, pows[POW_WIN]; +// sum + unsigned int nsum; + double sum; + } power[2]; +// + inline void AddPower (struct Power *p, double pow); + public: + void Init (void); + void GetPower (struct mad_pcm *pcm); + double GetLevel (void); + double GetPeak (void); +}; + +void +cLevel::Init (void) { - for(int l=0 ; l<2 ; l++) { - struct Power *p=&power[l]; - p->sum=p->powsum=0.0; p->wpow=p->npow=p->nsum=0; - for(int i=POW_WIN-1 ; i>=0 ; i--) p->pows[i]=0.0; + for (int l = 0; l < 2; l++) + { + struct Power *p = &power[l]; + p->sum = p->powsum = 0.0; + p->wpow = p->npow = p->nsum = 0; + for (int i = POW_WIN - 1; i >= 0; i--) + p->pows[i] = 0.0; } - maxpow=0.0; peak=0; + maxpow = 0.0; + peak = 0; } -void cLevel::GetPower(struct mad_pcm *pcm) + +void +cLevel::GetPower (struct mad_pcm *pcm) { - for(int i=0 ; i<pcm->channels ; i++) { - struct Power *p=&power[i]; - mad_fixed_t *data=pcm->samples[i]; - for(int n=pcm->length ; n>0 ; n--) { - if(*data < -peak) peak = -*data; - if(*data > peak) peak = *data; - double s=mad_f_todouble(*data++); - p->sum+=(s*s); - if(++(p->nsum)>=pcm->samplerate/100) { - AddPower(p,p->sum/(double)p->nsum); - p->sum=0.0; p->nsum=0; + for (int i = 0; i < pcm->channels; i++) + { + struct Power *p = &power[i]; + mad_fixed_t *data = pcm->samples[i]; + for (int n = pcm->length; n > 0; n--) + { + if (*data < -peak) + peak = -*data; + if (*data > peak) + peak = *data; + double s = mad_f_todouble (*data++); + p->sum += (s * s); + if (++(p->nsum) >= pcm->samplerate / 100) + { + AddPower (p, p->sum / (double) p->nsum); + p->sum = 0.0; + p->nsum = 0; + } } - } } } -void cLevel::AddPower(struct Power *p, double pow) + +void +cLevel::AddPower (struct Power *p, double pow) { - p->powsum+=pow; - if(p->npow>=POW_WIN) { - if(p->powsum>maxpow) maxpow=p->powsum; - p->powsum-=p->pows[p->wpow]; + p->powsum += pow; + if (p->npow >= POW_WIN) + { + if (p->powsum > maxpow) + maxpow = p->powsum; + p->powsum -= p->pows[p->wpow]; } - else p->npow++; - p->pows[p->wpow]=pow; - p->wpow=(p->wpow+1) % POW_WIN; + else + p->npow++; + p->pows[p->wpow] = pow; + p->wpow = (p->wpow + 1) % POW_WIN; } -double cLevel::GetLevel(void) -{ - if(maxpow<EPSILON) { - // Either this whole file has zero power, or was too short to ever - // fill the smoothing buffer. In the latter case, we need to just - // get maxpow from whatever data we did collect. - if(power[0].powsum>maxpow) maxpow=power[0].powsum; - if(power[1].powsum>maxpow) maxpow=power[1].powsum; +double +cLevel::GetLevel (void) +{ + if (maxpow < EPSILON) + { +// Either this whole file has zero power, or was too short to ever +// fill the smoothing buffer. In the latter case, we need to just +// get maxpow from whatever data we did collect. + + if (power[0].powsum > maxpow) + maxpow = power[0].powsum; + if (power[1].powsum > maxpow) + maxpow = power[1].powsum; } - double level=sqrt(maxpow/(double)POW_WIN); // adjust for the smoothing window size and root - printf("norm: new volumen level=%f peak=%f\n",level,mad_f_todouble(peak)); - return level; + // adjust for the smoothing window size and root + double level = sqrt (maxpow / (double) POW_WIN); + printf ("norm: new volumen level=%f peak=%f\n", level, + mad_f_todouble (peak)); + return level; } -double cLevel::GetPeak(void) + +double +cLevel::GetPeak (void) { - return mad_f_todouble(peak); + return mad_f_todouble (peak); } + // --- cNormalize ------------------------------------------------------------ -class cNormalize { -private: - mad_fixed_t gain; - double d_limlvl, one_limlvl; - mad_fixed_t limlvl; - bool dogain, dolimit; +class cNormalize +{ + private: + mad_fixed_t gain; + double d_limlvl, one_limlvl; + mad_fixed_t limlvl; + bool dogain, dolimit; #ifdef DEBUG - // stats - unsigned long limited, clipped, total; - mad_fixed_t peak; +// stats + unsigned long limited, clipped, total; + mad_fixed_t peak; #endif - // limiter +// limiter #ifdef USE_FAST_LIMITER - mad_fixed_t *table, tablestart; - int tablesize; - inline mad_fixed_t FastLimiter(mad_fixed_t x); + mad_fixed_t *table, tablestart; + int tablesize; + inline mad_fixed_t FastLimiter (mad_fixed_t x); #endif - inline mad_fixed_t Limiter(mad_fixed_t x); -public: - cNormalize(void); - ~cNormalize(); - void Init(double Level, double Peak); - void Stats(void); - void AddGain(struct mad_pcm *pcm); - }; - -cNormalize::cNormalize(void) + inline mad_fixed_t Limiter (mad_fixed_t x); + public: + cNormalize (void); + ~cNormalize (); + void Init (double Level, double Peak); + void Stats (void); + void AddGain (struct mad_pcm *pcm); +}; + +cNormalize::cNormalize (void) { - d_limlvl = (double)the_setup.LimiterLevel / 100.0; - one_limlvl = 1 - d_limlvl; - limlvl = mad_f_tofixed(d_limlvl); - printf( "norm: lim_lev=%f lim_acc=%d\n", d_limlvl, LIM_ACC ); + d_limlvl = (double) the_setup.LimiterLevel / 100.0; + one_limlvl = 1 - d_limlvl; + limlvl = mad_f_tofixed (d_limlvl); + printf ("norm: lim_lev=%f lim_acc=%d\n", d_limlvl, LIM_ACC); #ifdef USE_FAST_LIMITER - mad_fixed_t start=limlvl & ~(F_LIM_JMP-1); - tablestart=start; - tablesize=(unsigned int)(F_LIM_MAX-start)/F_LIM_JMP + 2; - table=new mad_fixed_t[tablesize]; - if(table) { - printf("norm: table size=%d start=%08x jump=%08x\n",tablesize,start,F_LIM_JMP); - for(int i=0 ; i<tablesize ; i++) { - table[i]=Limiter(start); - start+=F_LIM_JMP; - } - tablesize--; // avoid a -1 in FastLimiter() - - // do a quick accuracy check, just to be sure that FastLimiter() is working - // as expected :-) + mad_fixed_t start = limlvl & ~(F_LIM_JMP - 1); + tablestart = start; + tablesize = (unsigned int) (F_LIM_MAX - start) / F_LIM_JMP + 2; + table = new mad_fixed_t[tablesize]; + if (table) + { + printf ("norm: table size=%d start=%08x jump=%08x\n", tablesize, start, + F_LIM_JMP); + for (int i = 0; i < tablesize; i++) + { + table[i] = Limiter (start); + start += F_LIM_JMP; + } + tablesize--; // avoid a -1 in FastLimiter() + +// do a quick accuracy check, just to be sure that FastLimiter() is working +// as expected :-) #ifdef ACC_DUMP - FILE *out=fopen("/tmp/limiter","w"); + FILE *out = fopen ("/tmp/limiter", "w"); #endif - mad_fixed_t maxdiff=0; - for(mad_fixed_t x=F_LIM_MAX ; x>=limlvl ; x-=mad_f_tofixed(1e-4)) { - mad_fixed_t diff=mad_f_abs(Limiter(x)-FastLimiter(x)); - if(diff>maxdiff) maxdiff=diff; + mad_fixed_t maxdiff = 0; + for (mad_fixed_t x = F_LIM_MAX; x >= limlvl; x -= mad_f_tofixed (1e-4)) + { + mad_fixed_t diff = mad_f_abs (Limiter (x) - FastLimiter (x)); + if (diff > maxdiff) + maxdiff = diff; #ifdef ACC_DUMP - fprintf(out,"%0.10f\t%0.10f\t%0.10f\t%0.10f\t%0.10f\n", - mad_f_todouble(x),mad_f_todouble(Limiter(x)),mad_f_todouble(FastLimiter(x)),mad_f_todouble(diff),mad_f_todouble(maxdiff)); - if(ferror(out)) break; + fprintf (out, "%0.10f\t%0.10f\t%0.10f\t%0.10f\t%0.10f\n", + mad_f_todouble (x), mad_f_todouble (Limiter (x)), + mad_f_todouble (FastLimiter (x)), mad_f_todouble (diff), + mad_f_todouble (maxdiff)); + if (ferror (out)) + break; #endif - } + } #ifdef ACC_DUMP - fclose(out); + fclose (out); #endif - printf("norm: accuracy %.12f\n",mad_f_todouble(maxdiff)); - if(mad_f_todouble(maxdiff)>1e-6) - { - esyslog("ERROR: accuracy check failed, normalizer disabled"); - delete table; table=0; - } + printf ("norm: accuracy %.12f\n", mad_f_todouble (maxdiff)); + if (mad_f_todouble (maxdiff) > 1e-6) + { + esyslog ("ERROR: accuracy check failed, normalizer disabled"); + delete table; + table = 0; + } } - else esyslog("ERROR: no memory for lookup table, normalizer disabled"); -#endif // USE_FAST_LIMITER + else + esyslog ("ERROR: no memory for lookup table, normalizer disabled"); +#endif // USE_FAST_LIMITER } -cNormalize::~cNormalize() + +cNormalize::~cNormalize () { #ifdef USE_FAST_LIMITER - delete table; + delete table; #endif } -void cNormalize::Init(double Level, double Peak) + +void +cNormalize::Init (double Level, double Peak) { - double Target=(double)the_setup.TargetLevel/100.0; - double dgain=Target/Level; - if(dgain>MAX_GAIN) dgain=MAX_GAIN; - gain=mad_f_tofixed(dgain); - // Check if we actually need to apply a gain - dogain=(Target>0.0 && fabs(1-dgain)>MIN_GAIN); + double Target = (double) the_setup.TargetLevel / 100.0; + double dgain = Target / Level; + if (dgain > MAX_GAIN) + dgain = MAX_GAIN; + gain = mad_f_tofixed (dgain); +// Check if we actually need to apply a gain + dogain = (Target > 0.0 && fabs (1 - dgain) > MIN_GAIN); #ifdef USE_FAST_LIMITER - if(!table) dogain=false; + if (!table) + dogain = false; #endif - // Check if we actually need to do limiting: - // we have to if limiter is enabled, if gain>1 and if the peaks will clip. - dolimit=(d_limlvl<1.0 && dgain>1.0 && Peak*dgain>1.0); +// Check if we actually need to do limiting: +// we have to if limiter is enabled, if gain>1 and if the peaks will clip. + dolimit = (d_limlvl < 1.0 && dgain > 1.0 && Peak * dgain > 1.0); #ifdef DEBUG - printf("norm: gain=%f dogain=%d dolimit=%d (target=%f level=%f peak=%f)\n",dgain,dogain,dolimit,Target,Level,Peak); - limited=clipped=total=0; peak=0; + printf ("norm: gain=%f dogain=%d dolimit=%d (target=%f level=%f peak=%f)\n", + dgain, dogain, dolimit, Target, Level, Peak); + limited = clipped = total = 0; + peak = 0; #endif } -void cNormalize::Stats(void) + +void +cNormalize::Stats (void) { #ifdef DEBUG - if(total) - printf("norm: stats tot=%ld lim=%ld/%.3f%% clip=%ld/%.3f%% peak=%.3f\n", - total,limited,(double)limited/total*100.0,clipped,(double)clipped/total*100.0,mad_f_todouble(peak)); + if (total) + printf ("norm: stats tot=%ld lim=%ld/%.3f%% clip=%ld/%.3f%% peak=%.3f\n", + total, limited, (double) limited / total * 100.0, clipped, + (double) clipped / total * 100.0, mad_f_todouble (peak)); #endif } -mad_fixed_t cNormalize::Limiter(mad_fixed_t x) + +mad_fixed_t cNormalize::Limiter (mad_fixed_t x) { // Limiter function: // @@ -330,42 +399,57 @@ mad_fixed_t cNormalize::Limiter(mad_fixed_t x) // With limiter level = 0, this is equivalent to a tanh() function; // with limiter level = 1, this is equivalent to clipping. - if(x>limlvl) { + if (x > limlvl) + { #ifdef DEBUG - if(x>MAD_F_ONE) clipped++; - limited++; + if (x > MAD_F_ONE) + clipped++; + limited++; #endif - x=mad_f_tofixed(tanh((mad_f_todouble(x)-d_limlvl) / one_limlvl) * one_limlvl + d_limlvl); + x = + mad_f_tofixed (tanh ((mad_f_todouble (x) - d_limlvl) / one_limlvl) * + one_limlvl + d_limlvl); } - return x; + return x; } + #ifdef USE_FAST_LIMITER -mad_fixed_t cNormalize::FastLimiter(mad_fixed_t x) +mad_fixed_t cNormalize::FastLimiter (mad_fixed_t x) { // The fast algorithm is based on a linear interpolation between the // the values in the lookup table. Relays heavly on libmads fixed point format. - if(x>limlvl) { - int i=(unsigned int)(x-tablestart)/F_LIM_JMP; + if (x > limlvl) + { + int + i = (unsigned int) (x - tablestart) / F_LIM_JMP; #ifdef DEBUG - if(x>MAD_F_ONE) clipped++; - limited++; - if(i>=tablesize) printf("norm: overflow x=%f x-ts=%f i=%d tsize=%d\n", - mad_f_todouble(x),mad_f_todouble(x-tablestart),i,tablesize); + if (x > MAD_F_ONE) + clipped++; + limited++; + if (i >= tablesize) + printf ("norm: overflow x=%f x-ts=%f i=%d tsize=%d\n", + mad_f_todouble (x), mad_f_todouble (x - tablestart), i, + tablesize); #endif - mad_fixed_t r=x & (F_LIM_JMP-1); - x=MAD_F_ONE; - if(i<tablesize) { - mad_fixed_t *ptr=&table[i]; - x=*ptr; - mad_fixed_t d=*(ptr+1)-x; - //x+=mad_f_mul(d,r)<<LIM_ACC; // this is not accurate as mad_f_mul() does >>MAD_F_FRACBITS - // which is senseless in the case of following <<LIM_ACC. - x+=((long long)d*(long long)r)>>LIM_SHIFT; // better, don't know if works on all machines - } + mad_fixed_t + r = x & (F_LIM_JMP - 1); + x = MAD_F_ONE; + if (i < tablesize) + { + mad_fixed_t * + ptr = &table[i]; + x = *ptr; + mad_fixed_t + d = *(ptr + 1) - x; +//x+=mad_f_mul(d,r)<<LIM_ACC; // this is not accurate as mad_f_mul() does >>MAD_F_FRACBITS +// which is senseless in the case of following <<LIM_ACC. + // better, don't know if works on all machines + x += ((long long) d * (long long) r) >> LIM_SHIFT; + } } - return x; + return x; } #endif @@ -375,235 +459,296 @@ mad_fixed_t cNormalize::FastLimiter(mad_fixed_t x) #define LIMITER_FUNC Limiter #endif -void cNormalize::AddGain(struct mad_pcm *pcm) +void +cNormalize::AddGain (struct mad_pcm *pcm) { - if(dogain) { - for(int i=0 ; i<pcm->channels ; i++) { - mad_fixed_t *data=pcm->samples[i]; + if (dogain) + { + for (int i = 0; i < pcm->channels; i++) + { + mad_fixed_t *data = pcm->samples[i]; #ifdef DEBUG - total+=pcm->length; + total += pcm->length; #endif - if(dolimit) { - for(int n=pcm->length ; n>0 ; n--) { - mad_fixed_t s=mad_f_mul(*data,gain); - if(s<0) { - s=-s; + if (dolimit) + { + for (int n = pcm->length; n > 0; n--) + { + mad_fixed_t s = mad_f_mul (*data, gain); + if (s < 0) + { + s = -s; #ifdef DEBUG - if(s>peak) peak=s; + if (s > peak) + peak = s; #endif - s=LIMITER_FUNC(s); - s=-s; - } - else { + s = LIMITER_FUNC (s); + s = -s; + } + else + { #ifdef DEBUG - if(s>peak) peak=s; + if (s > peak) + peak = s; #endif - s=LIMITER_FUNC(s); + s = LIMITER_FUNC (s); + } + *data++ = s; + } } - *data++=s; - } - } - else { - for(int n=pcm->length ; n>0 ; n--) { - mad_fixed_t s=mad_f_mul(*data,gain); + else + { + for (int n = pcm->length; n > 0; n--) + { + mad_fixed_t s = mad_f_mul (*data, gain); #ifdef DEBUG - if(s>peak) peak=s; - else if(-s>peak) peak=-s; + if (s > peak) + peak = s; + else if (-s > peak) + peak = -s; #endif - if(s>MAD_F_ONE) s=MAD_F_ONE; // do clipping - if(s<-MAD_F_ONE) s=-MAD_F_ONE; - *data++=s; - } + if (s > MAD_F_ONE) + s = MAD_F_ONE; // do clipping + if (s < -MAD_F_ONE) + s = -MAD_F_ONE; + *data++ = s; + } + } } - } } } + // --- cScale ---------------------------------------------------------------- // The dither code has been adapted from the madplay project // (audio.c) found in the libmad distribution -enum eAudioMode { amRound, amDither }; +enum eAudioMode +{ amRound, amDither }; -class cScale { -private: - enum { MIN=-MAD_F_ONE, MAX=MAD_F_ONE - 1 }; +class cScale +{ + private: + enum + { MIN = -MAD_F_ONE, MAX = MAD_F_ONE - 1 }; #ifdef DEBUG - // audio stats - unsigned long clipped_samples; - mad_fixed_t peak_clipping; - mad_fixed_t peak_sample; +// audio stats + unsigned long clipped_samples; + mad_fixed_t peak_clipping; + mad_fixed_t peak_sample; #endif - // dither - struct dither { - mad_fixed_t error[3]; - mad_fixed_t random; - } leftD, rightD; - // - inline mad_fixed_t Clip(mad_fixed_t sample, bool stats=true); - inline signed long LinearRound(mad_fixed_t sample); - inline unsigned long Prng(unsigned long state); - inline signed long LinearDither(mad_fixed_t sample, struct dither *dither); -public: - void Init(void); - void Stats(void); - unsigned int ScaleBlock(unsigned char *data, unsigned int size, unsigned int &nsamples, const mad_fixed_t * &left, const mad_fixed_t * &right, eAudioMode mode); - }; - -void cScale::Init(void) +// dither + struct dither + { + mad_fixed_t error[3]; + mad_fixed_t random; + } leftD, rightD; +// + inline mad_fixed_t Clip (mad_fixed_t sample, bool stats = true); + inline signed long LinearRound (mad_fixed_t sample); + inline unsigned long Prng (unsigned long state); + inline signed long LinearDither (mad_fixed_t sample, struct dither *dither); + public: + void Init (void); + void Stats (void); + unsigned int ScaleBlock (unsigned char *data, unsigned int size, + unsigned int &nsamples, const mad_fixed_t * &left, + const mad_fixed_t * &right, eAudioMode mode); +}; + +void +cScale::Init (void) { #ifdef DEBUG - clipped_samples=0; peak_clipping=peak_sample=0; + clipped_samples = 0; + peak_clipping = peak_sample = 0; #endif - memset(&leftD,0,sizeof(leftD)); - memset(&rightD,0,sizeof(rightD)); + memset (&leftD, 0, sizeof (leftD)); + memset (&rightD, 0, sizeof (rightD)); } -void cScale::Stats(void) + +void +cScale::Stats (void) { #ifdef DEBUG - printf("mp3: scale stats clipped=%ld peak_clip=%f peak=%f\n", - clipped_samples,mad_f_todouble(peak_clipping),mad_f_todouble(peak_sample)); + printf ("mp3: scale stats clipped=%ld peak_clip=%f peak=%f\n", + clipped_samples, mad_f_todouble (peak_clipping), + mad_f_todouble (peak_sample)); #endif } + // gather signal statistics while clipping -mad_fixed_t cScale::Clip(mad_fixed_t sample, bool stats) +mad_fixed_t cScale::Clip (mad_fixed_t sample, bool stats) { #ifndef DEBUG - if (sample > MAX) sample = MAX; - if (sample < MIN) sample = MIN; + if (sample > MAX) + sample = MAX; + if (sample < MIN) + sample = MIN; #else - if(!stats) { - if (sample > MAX) sample = MAX; - if (sample < MIN) sample = MIN; + if (!stats) + { + if (sample > MAX) + sample = MAX; + if (sample < MIN) + sample = MIN; } - else { - if (sample >= peak_sample) { - if (sample > MAX) { - ++clipped_samples; - if (sample - MAX > peak_clipping) - peak_clipping = sample - MAX; - sample = MAX; + else + { + if (sample >= peak_sample) + { + if (sample > MAX) + { + ++clipped_samples; + if (sample - MAX > peak_clipping) + peak_clipping = sample - MAX; + sample = MAX; + } + peak_sample = sample; } - peak_sample = sample; - } - else if (sample < -peak_sample) { - if (sample < MIN) { - ++clipped_samples; - if (MIN - sample > peak_clipping) - peak_clipping = MIN - sample; - sample = MIN; + else if (sample < -peak_sample) + { + if (sample < MIN) + { + ++clipped_samples; + if (MIN - sample > peak_clipping) + peak_clipping = MIN - sample; + sample = MIN; + } + peak_sample = -sample; } - peak_sample = -sample; - } } #endif - return sample; + return sample; } + // generic linear sample quantize routine -signed long cScale::LinearRound(mad_fixed_t sample) +signed long +cScale::LinearRound (mad_fixed_t sample) { - // round - sample += (1L << (MAD_F_FRACBITS - OUT_BITS)); - // clip - sample=Clip(sample); - // quantize and scale - return sample >> (MAD_F_FRACBITS + 1 - OUT_BITS); +// round + sample += (1L << (MAD_F_FRACBITS - OUT_BITS)); +// clip + sample = Clip (sample); +// quantize and scale + return sample >> (MAD_F_FRACBITS + 1 - OUT_BITS); } + // 32-bit pseudo-random number generator -unsigned long cScale::Prng(unsigned long state) +unsigned long +cScale::Prng (unsigned long state) { - return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; + return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; } + // generic linear sample quantize and dither routine -signed long cScale::LinearDither(mad_fixed_t sample, struct dither *dither) +signed long +cScale::LinearDither (mad_fixed_t sample, struct dither *dither) { - unsigned int scalebits; - mad_fixed_t output, mask, random; - - // noise shape - sample += dither->error[0] - dither->error[1] + dither->error[2]; - dither->error[2] = dither->error[1]; - dither->error[1] = dither->error[0] / 2; - // bias - output = sample + (1L << (MAD_F_FRACBITS + 1 - OUT_BITS - 1)); - scalebits = MAD_F_FRACBITS + 1 - OUT_BITS; - mask = (1L << scalebits) - 1; - // dither - random = Prng(dither->random); - output += (random & mask) - (dither->random & mask); - dither->random = random; - // clip - output=Clip(output); - sample=Clip(sample,false); - // quantize - output &= ~mask; - // error feedback - dither->error[0] = sample - output; - // scale - return output >> scalebits; + unsigned int scalebits; + mad_fixed_t output, mask, random; + +// noise shape + sample += dither->error[0] - dither->error[1] + dither->error[2]; + dither->error[2] = dither->error[1]; + dither->error[1] = dither->error[0] / 2; +// bias + output = sample + (1L << (MAD_F_FRACBITS + 1 - OUT_BITS - 1)); + scalebits = MAD_F_FRACBITS + 1 - OUT_BITS; + mask = (1L << scalebits) - 1; +// dither + random = Prng (dither->random); + output += (random & mask) - (dither->random & mask); + dither->random = random; +// clip + output = Clip (output); + sample = Clip (sample, false); +// quantize + output &= ~mask; +// error feedback + dither->error[0] = sample - output; +// scale + return output >> scalebits; } + // write a block of signed 16-bit big-endian PCM samples -unsigned int cScale::ScaleBlock(unsigned char *data, unsigned int size, unsigned int &nsamples, const mad_fixed_t * &left, const mad_fixed_t * &right, eAudioMode mode) +unsigned int +cScale::ScaleBlock (unsigned char *data, unsigned int size, +unsigned int &nsamples, const mad_fixed_t * &left, +const mad_fixed_t * &right, eAudioMode mode) { - signed int sample; - unsigned int len, res; - - len=size/OUT_FACT; res=size; - if(len>nsamples) { len=nsamples; res=len*OUT_FACT; } - nsamples-=len; - - if(right) { // stereo - switch (mode) { - case amRound: - while (len--) { - sample = LinearRound(*left++); - *data++ = sample >> 8; - *data++ = sample >> 0; - sample = LinearRound(*right++); - *data++ = sample >> 8; - *data++ = sample >> 0; - } - break; - case amDither: - while (len--) { - sample = LinearDither(*left++,&leftD); - *data++ = sample >> 8; - *data++ = sample >> 0; - sample = LinearDither(*right++,&rightD); - *data++ = sample >> 8; - *data++ = sample >> 0; - } - break; - } + signed int sample; + unsigned int len, res; + + len = size / OUT_FACT; + res = size; + if (len > nsamples) + { + len = nsamples; + res = len * OUT_FACT; } - else { // mono, duplicate left channel - switch (mode) { - case amRound: - while (len--) { - sample = LinearRound(*left++); - *data++ = sample >> 8; - *data++ = sample >> 0; - *data++ = sample >> 8; - *data++ = sample >> 0; - } - break; - case amDither: - while (len--) { - sample = LinearDither(*left++,&leftD); - *data++ = sample >> 8; - *data++ = sample >> 0; - *data++ = sample >> 8; - *data++ = sample >> 0; - } - break; - } + nsamples -= len; + + if (right) + { // stereo + switch (mode) + { + case amRound: + while (len--) + { + sample = LinearRound (*left++); + *data++ = sample >> 8; + *data++ = sample >> 0; + sample = LinearRound (*right++); + *data++ = sample >> 8; + *data++ = sample >> 0; + } + break; + case amDither: + while (len--) + { + sample = LinearDither (*left++, &leftD); + *data++ = sample >> 8; + *data++ = sample >> 0; + sample = LinearDither (*right++, &rightD); + *data++ = sample >> 8; + *data++ = sample >> 0; + } + break; + } + } + else + { // mono, duplicate left channel + switch (mode) + { + case amRound: + while (len--) + { + sample = LinearRound (*left++); + *data++ = sample >> 8; + *data++ = sample >> 0; + *data++ = sample >> 8; + *data++ = sample >> 0; + } + break; + case amDither: + while (len--) + { + sample = LinearDither (*left++, &leftD); + *data++ = sample >> 8; + *data++ = sample >> 0; + *data++ = sample >> 8; + *data++ = sample >> 0; + } + break; + } } - return res; + return res; } diff --git a/vdr_stream.c b/vdr_stream.c index 0c12075..4f79224 100644 --- a/vdr_stream.c +++ b/vdr_stream.c @@ -37,295 +37,315 @@ #include <sys/mman.h> #endif -#define DEFAULT_PORT 80 // default port for streaming (HTTP) - +#define DEFAULT_PORT 80 // default port for streaming (HTTP) // --- mgStream ----------------------------------------------------------------- -mgStream::mgStream( std::string filename ) - : m_filename( filename ) +mgStream::mgStream (std::string filename):m_filename (filename) { - m_fd = -1; - m_ismmap = false; - m_buffer = 0; + m_fd = -1; + m_ismmap = false; + m_buffer = 0; } -mgStream::~mgStream() + +mgStream::~mgStream () { - close(); + close (); } -bool mgStream::open(bool log) + +bool mgStream::open (bool log) { - if( m_fd >= 0 ) + if (m_fd >= 0) { - return seek(); + return seek (); } - // just check, whether file exists? - if( fileinfo( log ) ) +// just check, whether file exists? + if (fileinfo (log)) { - printf( "mgStream::open: fileinfo == true\n"); - - if( ( m_fd = ::open( m_filename.c_str(), O_RDONLY ) ) >=0 ) - { - printf( "mgStream::open: file opened\n" ); +// printf ("mgStream::open: fileinfo == true\n"); - m_buffpos = m_readpos = 0; - m_fill = 0; + if ((m_fd =::open (m_filename.c_str (), O_RDONLY)) >= 0) + { + //printf ("mgStream::open: file opened\n"); - printf( "mgStream::open: buffpos, readpos, fill set\n" ); + m_buffpos = m_readpos = 0; + m_fill = 0; - /* -#ifdef USE_MMAP - if( m_filesize <= MAX_MMAP_SIZE ) - { - m_buffer = (unsigned char*)mmap( 0, m_filesize, PROT_READ, - MAP_SHARED, m_fd, 0 ); - if( m_buffer != MAP_FAILED ) - { - m_ismmap = true; - return true; - } - else - { - dsyslog("mmap() failed for %s: %s", m_filename.c_str(), strerror(errno) ); - } - } + //printf ("mgStream::open: buffpos, readpos, fill set\n"); + +/* + #ifdef USE_MMAP + if( m_filesize <= MAX_MMAP_SIZE ) + { + m_buffer = (unsigned char*)mmap( 0, m_filesize, PROT_READ, + MAP_SHARED, m_fd, 0 ); + if( m_buffer != MAP_FAILED ) + { + m_ismmap = true; + return true; + } +else +{ +dsyslog("mmap() failed for %s: %s", m_filename.c_str(), strerror(errno) ); +} +} #endif - */ - printf( "mgStream::open: allocating buffer: %d\n", MP3FILE_BUFSIZE ); - m_buffer = new unsigned char[MP3FILE_BUFSIZE]; - printf( "mgStream::open: buffer allocated\n" ); - - if( m_buffer ) - { - printf( "mgStream::open: buffer allocated, returning true\n" ); - - return true; - } - else - { - esyslog("ERROR: not enough memory for buffer: %s", m_filename.c_str() ); - } - } - else - { - if( log ) - { - esyslog("ERROR: failed to open file %s: %s", m_filename.c_str(), strerror(errno) ); - } - } +*/ + //printf ("mgStream::open: allocating buffer: %d\n", MP3FILE_BUFSIZE); + m_buffer = new unsigned char[MP3FILE_BUFSIZE]; + //printf ("mgStream::open: buffer allocated\n"); + + if (m_buffer) + { + //printf ("mgStream::open: buffer allocated, returning true\n"); + + return true; + } + else + { + esyslog ("ERROR: not enough memory for buffer: %s", + m_filename.c_str ()); + } + } + else + { + if (log) + { + esyslog ("ERROR: failed to open file %s: %s", + m_filename.c_str (), strerror (errno)); + } + } } - close(); - printf( "mgStream::open: returning false\n" ); - return false; + close (); + //printf ("mgStream::open: returning false\n"); + return false; } -void mgStream::close(void) + +void +mgStream::close (void) { #ifdef USE_MMAP - if( m_ismmap ) - { - munmap( m_buffer, m_filesize ); - m_buffer = 0; - m_ismmap = false; + if (m_ismmap) + { + munmap (m_buffer, m_filesize); + m_buffer = 0; + m_ismmap = false; } - else + else { #endif - delete m_buffer; - m_buffer = 0; + delete m_buffer; + m_buffer = 0; #ifdef USE_MMAP } #endif - if( m_fd >= 0 ) - { - ::close( m_fd ); - m_fd = -1; + if (m_fd >= 0) + { + ::close (m_fd); + m_fd = -1; } } -bool mgStream::seek(unsigned long long pos) + +bool mgStream::seek (unsigned long long pos) { - printf( "mgStream::seek\n" ); - if( m_fd >= 0 && pos >= 0 && pos <= m_filesize ) + //printf ("mgStream::seek\n"); + if (m_fd >= 0 && pos >= 0 && pos <= m_filesize) { - printf( "mgStream::seek valid file and position detected\n" ); - - m_buffpos = 0; - m_fill = 0; - - if( m_ismmap ) - { - m_readpos = pos; - - printf( "mgStream::seek: returning true\n" ); - return true; - } - else - { - if( ( m_readpos = lseek64( m_fd, pos, SEEK_SET ) ) >=0 ) - { - if( m_readpos != pos ) - { - dsyslog( "seek mismatch in %s, wanted %lld, got %lld", m_filename.c_str(), pos, m_readpos ); - } - printf( "mgStream::seek: returning true\n" ); - return true; - } - else - { - esyslog( "ERROR: seeking failed in %s: %d,%s", m_filename.c_str(), errno, strerror(errno) ); - } - } + //printf ("mgStream::seek valid file and position detected\n"); + + m_buffpos = 0; + m_fill = 0; + + if (m_ismmap) + { + m_readpos = pos; + + //printf ("mgStream::seek: returning true\n"); + return true; + } + else + { + if ((m_readpos = lseek64 (m_fd, pos, SEEK_SET)) >= 0) + { + if (m_readpos != pos) + { + dsyslog ("seek mismatch in %s, wanted %lld, got %lld", + m_filename.c_str (), pos, m_readpos); + } + //printf ("mgStream::seek: returning true\n"); + return true; + } + else + { + esyslog ("ERROR: seeking failed in %s: %d,%s", + m_filename.c_str (), errno, strerror (errno)); + } + } } - else + else { - printf( "mp3: bad seek call fd=%d pos=%lld name=%s\n", m_fd, pos, m_filename.c_str() ); + //printf ("mp3: bad seek call fd=%d pos=%lld name=%s\n", m_fd, pos, + //m_filename.c_str ()); } - - printf( "mgStream::seek: returning false\n" ); - return false; + + //printf ("mgStream::seek: returning false\n"); + return false; } -bool mgStream::stream(unsigned char * &data, - unsigned long &len, - const unsigned char *rest) + +bool +mgStream::stream (unsigned char *&data, +unsigned long &len, const unsigned char *rest) { - if( m_fd >= 0 ) + if (m_fd >= 0) { - if( m_readpos < m_filesize ) - { - if( m_ismmap ) - { - if( rest && m_fill ) - { - m_readpos = (rest - m_buffer); // take care of remaining data - } - m_fill = m_filesize - m_readpos; - data = m_buffer + m_readpos; - len = m_fill; - m_buffpos = m_readpos; - m_readpos += m_fill; - - return true; - } - else - { - if( rest && m_fill ) - { // copy remaining data to start of buffer - m_fill -= ( rest - m_buffer); // remaing bytes - memmove( m_buffer, rest, m_fill ); - } - else - { - m_fill = 0; - } - - int r; - do - { - r = read( m_fd, m_buffer + m_fill, - MP3FILE_BUFSIZE - m_fill ); - } while( r == -1 && errno == EINTR ); - - if( r >= 0 ) - { - m_buffpos = m_readpos - m_fill; - m_readpos += r; - m_fill += r; - data = m_buffer; - len = m_fill; - - return true; - } - else - { - esyslog("ERROR: read failed in %s: %d,%s", m_filename.c_str(), errno, strerror(errno) ); - } - } - } - else - { - len = 0; - return true; - } + if (m_readpos < m_filesize) + { + if (m_ismmap) + { + if (rest && m_fill) + { + m_readpos = (rest - m_buffer);// take care of remaining data + } + m_fill = m_filesize - m_readpos; + data = m_buffer + m_readpos; + len = m_fill; + m_buffpos = m_readpos; + m_readpos += m_fill; + + return true; + } + else + { + if (rest && m_fill) + { // copy remaining data to start of buffer + m_fill -= (rest - m_buffer); // remaing bytes + memmove (m_buffer, rest, m_fill); + } + else + { + m_fill = 0; + } + + int r; + do + { + r = read (m_fd, m_buffer + m_fill, + MP3FILE_BUFSIZE - m_fill); + } + while (r == -1 && errno == EINTR); + + if (r >= 0) + { + m_buffpos = m_readpos - m_fill; + m_readpos += r; + m_fill += r; + data = m_buffer; + len = m_fill; + + return true; + } + else + { + esyslog ("ERROR: read failed in %s: %d,%s", + m_filename.c_str (), errno, strerror (errno)); + } + } + } + else + { + len = 0; + return true; + } } - return false; + return false; } -bool mgStream::removable() + +bool mgStream::removable () { - // we do not handle removable media at this time - return false; +// we do not handle removable media at this time + return false; } -bool mgStream::fileinfo( bool log ) + +bool mgStream::fileinfo (bool log) { - struct stat64 ds; - - if( !stat64( m_filename.c_str(), &ds ) ) + struct stat64 + ds; + + if (!stat64 (m_filename.c_str (), &ds)) { - printf( "mgStream::fileinfo: stat64 == 0\n" ); - - if( S_ISREG( ds.st_mode ) ) - { - m_fsID = ""; - m_fsType = 0; - - struct statfs64 sfs; - - if( !statfs64( m_filename.c_str(), &sfs) ) - { - if( removable() ) - { - char *tmpbuf; - asprintf( &tmpbuf, "%llx:%llx", sfs.f_blocks, sfs.f_files ); - m_fsID = tmpbuf; - free( tmpbuf ); - } - m_fsType = sfs.f_type; - } - else - { - if( errno != ENOSYS && log ) - { - esyslog("ERROR: can't statfs %s: %s", m_filename.c_str(), strerror(errno) ); - } - } - - m_filesize = ds.st_size; - m_ctime = ds.st_ctime; - + //printf ("mgStream::fileinfo: stat64 == 0\n"); + + if (S_ISREG (ds.st_mode)) + { + m_fsID = ""; + m_fsType = 0; + + struct statfs64 + sfs; + + if (!statfs64 (m_filename.c_str (), &sfs)) + { + if (removable ()) + { + char * + tmpbuf; + asprintf (&tmpbuf, "%llx:%llx", sfs.f_blocks, sfs.f_files); + m_fsID = tmpbuf; + free (tmpbuf); + } + m_fsType = sfs.f_type; + } + else + { + if (errno != ENOSYS && log) + { + esyslog ("ERROR: can't statfs %s: %s", m_filename.c_str (), + strerror (errno)); + } + } + + m_filesize = ds.st_size; + m_ctime = ds.st_ctime; + #ifdef CDFS_MAGIC - if( m_fsType == CDFS_MAGIC ) - { - m_ctime=0; // CDFS returns mount time as ctime - } + if (m_fsType == CDFS_MAGIC) + { + m_ctime = 0; // CDFS returns mount time as ctime + } #endif - // infodone tells that info has been read, like a cache flag - // InfoDone(); - return true; +// infodone tells that info has been read, like a cache flag +// InfoDone(); + return true; + } + else + { + if (log) + { + esyslog ("ERROR: %s is not a regular file", + m_filename.c_str ()); + } } - else - { - if(log) - { - esyslog("ERROR: %s is not a regular file", m_filename.c_str() ); - } - } } - else + else { - if(log) - { - esyslog("ERROR: can't stat %s: %s", m_filename.c_str(), strerror(errno) ); - } + if (log) + { + esyslog ("ERROR: can't stat %s: %s", m_filename.c_str (), + strerror (errno)); + } - printf( "mgStream::fileinfo: stat64 != 0 for %s\n", m_filename.c_str() ); + //printf ("mgStream::fileinfo: stat64 != 0 for %s\n", + // m_filename.c_str ()); } - - return false; + + return false; } diff --git a/vdr_stream.h b/vdr_stream.h index 68f7ea7..6b1769c 100644 --- a/vdr_stream.h +++ b/vdr_stream.h @@ -3,11 +3,11 @@ * \brief Definitions of media streams * * \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_stream.h,v 1.2 2004/05/28 15:29:19 lvw Exp $ + * $Id$ * * Adapted from * MP3/MPlayer plugin to VDR (C++) @@ -25,33 +25,36 @@ class cNet; // ---------------------------------------------------------------- -class mgStream // : public mgFileInfo +class mgStream // : public mgFileInfo { -private: - int m_fd; - bool m_ismmap; - - // from cFileInfo - std::string m_filename, m_fsID; - unsigned long long m_filesize; - time_t m_ctime; - long m_fsType; - - bool fileinfo( bool log ); - bool removable(); - -protected: - unsigned char *m_buffer; - unsigned long long m_readpos, m_buffpos; - unsigned long m_fill; -public: - mgStream( std::string filename ); - virtual ~mgStream(); - virtual bool open(bool log = true); - virtual void close(); - virtual bool stream( unsigned char *&data, unsigned long &len, const unsigned char *rest=NULL ); - virtual bool seek( unsigned long long pos = 0 ); - virtual unsigned long long bufferPos() { return m_buffpos; } + private: + int m_fd; + bool m_ismmap; + +// from cFileInfo + std::string m_filename, m_fsID; + unsigned long long m_filesize; + time_t m_ctime; + long m_fsType; + + bool fileinfo (bool log); + bool removable (); + + protected: + unsigned char *m_buffer; + unsigned long long m_readpos, m_buffpos; + unsigned long m_fill; + public: + mgStream (std::string filename); + virtual ~ mgStream (); + virtual bool open (bool log = true); + virtual void close (); + virtual bool stream (unsigned char *&data, unsigned long &len, + const unsigned char *rest = NULL); + virtual bool seek (unsigned long long pos = 0); + virtual unsigned long long bufferPos () + { + return m_buffpos; + } }; - -#endif //___STREAM_H +#endif //___STREAM_H |