summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoerg Bornkessel <hd_brummy@gentoo.org>2014-04-26 19:15:40 +0200
committerJoerg Bornkessel <hd_brummy@gentoo.org>2014-04-26 19:15:40 +0200
commitd45bc4bed686d1d0c2daff22a66dfa0394fde4fb (patch)
tree75b7a38c993c0c5761764a375fa822190729a402
parent99628e3cec79c4b5888056ce4be3b92a38eddf47 (diff)
downloadvdr-plugin-audiorecorder-d45bc4bed686d1d0c2daff22a66dfa0394fde4fb.tar.gz
vdr-plugin-audiorecorder-d45bc4bed686d1d0c2daff22a66dfa0394fde4fb.tar.bz2
initial commit
-rw-r--r--COPYING340
-rw-r--r--HISTORY90
-rw-r--r--METAGS.TAG (renamed from push-test)0
-rw-r--r--Makefile.obsolete136
-rw-r--r--README58
-rw-r--r--a-tools.h22
-rw-r--r--audioreceiver.c259
-rw-r--r--audioreceiver.h63
-rw-r--r--audiorecorder.c345
-rw-r--r--audiorecorder.h63
-rw-r--r--browse-item.c69
-rw-r--r--browse-item.h44
-rw-r--r--browse.c638
-rw-r--r--browse.h71
-rw-r--r--cache.c232
-rw-r--r--cache.h55
-rw-r--r--column.c29
-rw-r--r--column.h58
-rw-r--r--contrib/audiorecorder.conf12
-rwxr-xr-xcontrib/postproc.pl144
-rwxr-xr-xcontrib/set_path35
-rw-r--r--convert.c154
-rw-r--r--convert.h37
-rw-r--r--dispatcher.c435
-rw-r--r--dispatcher.h93
-rw-r--r--external-player.h15
-rw-r--r--mainmenu.c90
-rw-r--r--mainmenu.h29
-rw-r--r--mpa-frame.c55
-rw-r--r--mpa-frame.h24
-rw-r--r--po/de_DE.po240
-rw-r--r--postdata.c135
-rw-r--r--postdata.h62
-rw-r--r--postproc.c380
-rw-r--r--postproc.h48
-rw-r--r--rds.c386
-rw-r--r--rds.h60
-rw-r--r--recstat.h17
-rw-r--r--service.h27
-rw-r--r--setup.c104
-rw-r--r--setup.h56
-rw-r--r--trackinfo.c360
-rw-r--r--trackinfo.h69
-rw-r--r--xml-base.c104
-rw-r--r--xml-base.h37
-rw-r--r--xml-cache.c280
-rw-r--r--xml-cache.h33
47 files changed, 6093 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..5b6e7c6
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/HISTORY b/HISTORY
new file mode 100644
index 0000000..34f28ca
--- /dev/null
+++ b/HISTORY
@@ -0,0 +1,90 @@
+VDR Plugin 'audiorecorder' Revision History
+-------------------------------------------
+
+2006-06-16: Version 0.1.0-pre1
+
+- Initial revision.
+
+2006-06-26: Version 0.1.0-pre2
+
+- replaced localtime with thread-safe localtime_r (cAudioreceiver::get_path()).
+- try to catch wrong rtp-tags (cRds::correct_rtp_tags()).
+- add support for reencoding into mp3 format.
+
+2006-06-26: Version 0.1.0-pre3
+
+- a small bugfix in cRds::decode_rtp().
+
+2006-07-17: Version 0.1.0-pre4
+
+- complete rewrite of the recording stuff. now there are up to !!! 12 !!!
+ parallel recordings possible (cDispatcher()).
+- better/more checks of the rtp_tags (cRds::correct_rtp_tag()).
+- fixed a bug in cRds::set_next_frame().
+- some cosmetic changes.
+- introduced service-interface for communication with other plugins
+ (you can use the header file service.h).
+- reduced the verbosity of the plugin. debugging could be enabled on stdout with
+ the commandline argument -d or --debug=.
+
+2006-09-01: Version 0.1.0-pre5
+
+- added casts to uint64_t to avoid an integer overflow (suggested by egal &
+ lordjaxom on www.vdrportal.de, cDispatcher::check_free_disc_space()).
+- added "min. free disc space (in mb)" to setup menu. the recording is stopped
+ if the free space on the recording-directory is smaller than this value
+ (suggested by egal on www.vdrportal.de).
+- added "max. tracks in queue" to setup menu. the recording is stopped if this
+ value is achived. recording is startet again at 25 % of this value
+ (suggested by egal on www.vdrportal.de).
+- now artist and title are converted to capital letters (for better detecting
+ of doubled recordings).
+- after switching a channel, the next receiver is attached after at minimum
+ 30 seconds (thx to andipiel from www.vdrportal.de, cDispatcher()).
+- removed track.c and track.h
+- introduced caching (xml-base.[c,h], xml-cache.[c,h] and cache.[c,h]).
+ tinyxml is used for the xml-handling, so thx a lot to the authors ...
+ ... look at http://www.grinninglizard.com/tinyxml/ for more informations.
+- introduced menu and browser (menu.[c,h], browse[c,h], browse-item[c,h])
+- code cleanup, replaced most c-functions with the according c++/stl function.
+- add "pause if osd is open" to setup menu.
+- moved hr3, hr1 and swr3 to the end of the channels-list (dispatcher.c)
+- added possible album, track or year-values (trackinfo.h)
+
+2006-09-05: Version 0.1.0-pre6
+
+- fixed a possible crash in cXmlCache::copy_to_objects()
+- added the namespace "a-land" to tinyxml to avoid a segfault when used together
+ with the setup plugin/path
+
++HISTORY.magicamun
+
+2007-03-08: Version 0.1.0-pre6a
+
+- adjustable setup for "UpperCase" or not in the naming of the song.
+- 3 possible namings of songs introduced, subdirectories are possible too.
+- adjustable number of copies of songs on disk.
+- preparation for setting of channels via name/provider.
+
+2007-03-25: Version 0.1.0-pre6b
+- config file "audiorecorder.conf" in the plugin-directory of your
+ VDR added, to setup the used channels.
+
+2007-03-25: Version 0.1.0-pre6c
+- multilanguage support (at the moment only english and german)
+- Adjusting/correcting the cache-functions to allow subdirectories
+- Reading of the xml-cache reprogrammed as a thread - to speed up
+ the start of the VDR.
+
+2007-05-26: Version 0.1.0-pre7 (for 1.4.x)
+2007-05-26: Version 0.1.0-pre8 (für 1.5.x)
+- optional naming of the songs via external script.
+
+2007-06-08: Version 0.1.0-pre9
+- the plugin is now acting better if the audiorecorder.conf is missing.
+- caches only the defined codecs (mp2, mp3).
+- relative pathes in the xml-cache.
+- crash fixed in the script post processing if there is no string returned.
+- channel (#5) and event (#6) added to the parameter list of the
+ external script, bitrates are now given correctly.
+
diff --git a/push-test b/METAGS.TAG
index e69de29..e69de29 100644
--- a/push-test
+++ b/METAGS.TAG
diff --git a/Makefile.obsolete b/Makefile.obsolete
new file mode 100644
index 0000000..16c6eac
--- /dev/null
+++ b/Makefile.obsolete
@@ -0,0 +1,136 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+# $Id$
+
+# The official name of this plugin.
+# This name will be used in the '-P...' option of VDR to load the plugin.
+# By default the main source file also carries this name.
+#
+PLUGIN = audiorecorder
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'const char \*cPluginAudiorecorder::VERSION *=' $(PLUGIN).c | awk '{ print $$5 }' | sed -e 's/[";]//g')
+
+### The C++ compiler and options:
+
+CXX ?= g++
+CXXFLAGS ?= -fPIC -g -O2 -Wall -Woverloaded-virtual
+
+### The directory environment:
+
+VDRDIR = ../../..
+LIBDIR = ../../lib
+TMPDIR = /tmp
+
+FFMDIR = /usr/include
+
+### Allow user defined options to overwrite defaults:
+
+-include $(VDRDIR)/Make.config
+
+### The version number of VDR's plugin API (taken from VDR's "config.h"):
+
+APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h)
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+
+### Includes, Libs and Defines (add further entries here):
+
+INCLUDES += -I$(VDRDIR)/include -I$(DVBDIR)/include $(shell taglib-config --cflags)
+
+LIBS = $(shell taglib-config --libs)
+
+DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' -DTIXML_USE_STL
+
+ifdef AUDIORECORDER_DEVEL
+ DEFINES += -DAUDIORECORDER_DEVEL
+endif
+
+# ffmpeg
+AVCODEC_INC = $(shell pkg-config libavcodec --cflags | sed -e 's/ //g')
+ifeq ($(strip $(AVCODEC_INC)),)
+ INCLUDES += -I$(FFMDIR) -I$(FFMDIR)/libavcodec
+else
+ INCLUDES += $(AVCODEC_INC)
+endif
+AVCODEC_LIBS = $(shell pkg-config libavcodec --libs)
+ifeq ($(strip $(AVCODEC_LIBS)),)
+ LIBS += -lavcodec
+else
+ LIBS += $(AVCODEC_LIBS)
+endif
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o mainmenu.o browse.o browse-item.o column.o dispatcher.o audioreceiver.o postdata.o trackinfo.o postproc.o rds.o convert.o cache.o xml-cache.o xml-base.o setup.o mpa-frame.o tinyxml/tinyxml.o tinyxml/tinyxmlerror.o tinyxml/tinyxmlparser.o audiorecorder_i18n.o
+
+### Implicit rules:
+
+%.o: %.c
+ $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
+
+# Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+ @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
+
+-include $(DEPFILE)
+
+### Internationalization (I18N):
+
+PODIR = po
+LOCALEDIR = $(VDRDIR)/locale
+I18Npo = $(wildcard $(PODIR)/*.po)
+I18Nmo = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file))))
+I18Ndirs = $(notdir $(foreach file, $(I18Npo), $(basename $(file))))
+I18Npot = $(PODIR)/$(PLUGIN).pot
+I18Nvdrmo = vdr-$(PLUGIN).mo
+ifeq ($(strip $(APIVERSION)),1.5.7)
+ I18Nvdrmo = $(PLUGIN).mo
+endif
+
+%.mo: %.po
+ msgfmt -c -o $@ $<
+
+$(I18Npot): $(OBJS:%.o=%.c)
+ xgettext -C -cTRANSLATORS --no-wrap -F -k -ktr -ktrNOOP --msgid-bugs-address='<cwieninger@gmx.de>' -o $@ $(OBJS:%.o=%.c)
+
+$(I18Npo): $(I18Npot)
+ msgmerge -U --no-wrap -F --backup=none -q $@ $<
+
+i18n: $(I18Nmo)
+ @mkdir -p $(LOCALEDIR)
+ for i in $(I18Ndirs); do\
+ mkdir -p $(LOCALEDIR)/$$i/LC_MESSAGES;\
+ cp $(PODIR)/$$i.mo $(LOCALEDIR)/$$i/LC_MESSAGES/$(I18Nvdrmo);\
+ done
+
+generate-i18n: i18n-template.h $(I18Npot) $(I18Npo) buildutil/pot2i18n.pl
+ buildutil/pot2i18n.pl $(I18Npot) i18n-template.h > i18n-generated.h
+
+### Targets:
+
+all: libvdr-$(PLUGIN).so i18n
+
+libvdr-$(PLUGIN).so: $(OBJS)
+ $(CXX) $(CXXFLAGS) -shared $(OBJS) $(LIBS) -o $@
+ @cp $@ $(LIBDIR)/$@.$(APIVERSION)
+
+dist: clean
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @mkdir $(TMPDIR)/$(ARCHIVE)
+ @cp -a * $(TMPDIR)/$(ARCHIVE)
+ @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @echo Distribution package created as $(PACKAGE).tgz
+
+clean:
+ @-rm -f $(PODIR)/*.mo $(PODIR)/*.pot
+ @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~
diff --git a/README b/README
new file mode 100644
index 0000000..cddbd9c
--- /dev/null
+++ b/README
@@ -0,0 +1,58 @@
+This is a "plugin" for the Video Disk Recorder (VDR).
+
+Written by: herbert attenberger <herbsl at a-land.de>
+
+Project's homepage: <http://www.a-land.de>
+
+Latest version available at: <http://www.a-land.de>
+
+See the file COPYING for license information.
+
+Description:
+Floods your disc with music! This plugin records songs with artist, title and ID3-Tag. The naming and cutting of the songs uses the "Radiotext plus" feature of the ARD channels "DAS MODUL", Eins_Live, YOU_FM, WDR_2, SWR3 etc., as long as the vdr is tuned to the transponder or if there is a unused DVB-card.
+
+As codec you can choose between the native format mp2 MPEG-1_Audio_Layer_2 and mp3 (mit adjustable bitrate). More codecs are planned for the future.
+
+Additionally you can add a fade-in and/or fade-out effect, to reduce unwanted noises at the beginning or the end of the song.
+
+TODO: Add other recording mechanisms, e.g. PayRadio channels with title/artist in the EPG as Premiere, DMX, ....
+
+Parameters:
+-r dir, --recdir=dir set recording-directory to 'dir'.
+-d level, --debug=level set debugging level (default: 0),
+ all stuff is written to stdout.
+ 0 = off
+ 1 = only errors
+ 2 = errors and further infos
+
++ Patches to the original plugin
+
+Written by: magicamun
+
+Description:
+Additions to the original plugin are:
+
+- adjustable setup for "UpperCase" or not in the naming of the song.
+- 3 possible namings of songs introduced, subdirectories are possible too.
+- adjustable number of copies of songs on disk.
+- config file "audiorecorder.conf" in the plugin-directory of your vdr added, to setup the used channels.
+- multilanguage support (at the moment only english and german)
+- Reading of the xml-cache reprogrammed as a thread - to speed up the start of the VDR.
+- optional naming of the songs via external script.
+- relative pathes in the xml-cache.
+
+Parameters:
+-r dir, --recdir=dir set recording-directory to 'dir'.
+-d level, --debug=level set debugging level (default: 0),
+ all stuff is written to stdout.
+ 0 = off
+ 1 = only errors
+ 2 = errors and further infos
+-p script, --postproc=script set script (default: none)
+ for postprocessing.
+
+Installation:
+Additional to the installation hints of the pre5, copy the "audiorecorder.conf" in the plugin-directory of your VDR.
+
+Others:
+Other recording mechanism via title/artist in the EPG for Premiere programmed by Frankman (search for P-Rex)
diff --git a/a-tools.h b/a-tools.h
new file mode 100644
index 0000000..4a00eda
--- /dev/null
+++ b/a-tools.h
@@ -0,0 +1,22 @@
+/*
+ * a-tools.h:
+ */
+
+
+#ifndef __A_TOOLS_H
+#define __A_TOOLS_H
+
+#define KB(x) x * 1024
+#define MB(x) KB(x) * 1024
+
+#define DELETE(x) if (x != NULL) { delete x; x = NULL; }
+
+typedef unsigned char uchar;
+
+typedef struct abuffer {
+ uchar *data;
+ int length;
+ int offset;
+} abuffer;
+
+#endif /* __A_TOOLS_H */
diff --git a/audioreceiver.c b/audioreceiver.c
new file mode 100644
index 0000000..43c8723
--- /dev/null
+++ b/audioreceiver.c
@@ -0,0 +1,259 @@
+/*
+ * audioreceiver.c
+ */
+
+#include "audioreceiver.h"
+#include "postproc.h"
+#include "audiorecorder.h"
+
+
+/* --- cAudioReceiver ------------------------------------------------------- */
+
+cAudioReceiver::cAudioReceiver(const cChannel *_channel)
+:cReceiver(_channel->GetChannelID(), -2, _channel->Apid(0)), cThread()
+{
+ channel = _channel;
+
+ active = false;
+
+ pes_sync = false;
+ device_number = -1;
+ recstat = recWait;
+
+ buffer = NULL;
+ rds = NULL;
+ postdata = NULL;
+
+ dsyslog("[audiorecorder]: receiver for channel <%s> created on pid %d "
+ "(%s, %s())", channel->Name(), channel->Apid(0), __FILE__,
+ __func__);
+}
+
+
+cAudioReceiver::~cAudioReceiver(void)
+{
+ Activate(false);
+
+ DELETE(buffer);
+ DELETE(rds);
+ DELETE(postdata);
+}
+
+
+void cAudioReceiver::Receive(uchar *data, int length)
+{
+ int ts_header_len, header_len, data_len, rb_len;
+
+ if (! active || data[0] != 0x47)
+ return;
+
+ /* check adaptation field */
+ switch (data[3] & 0x30) {
+ case 0x10:
+ /* no adaptation field, payload only */
+ ts_header_len = 4;
+ break;
+ case 0x30:
+ /* adaptation field followed by payload */
+ ts_header_len = data[4] + 5;
+ break;
+ default:
+ return;
+ }
+
+ header_len = ts_header_len;
+
+ /* check payload unit start indicator */
+ if ((data[1] & 0x40) == 0x40) {
+ /* ts-header is followed by a pes-header */
+ header_len += data[ts_header_len + 8] + 9;
+ pes_sync = true;
+ }
+
+ if (! pes_sync)
+ return;
+
+ /* put mp2-data into the ringbuffer */
+ data_len = length - header_len;
+ rb_len = buffer->Put(data + header_len, data_len);
+
+ if (data_len != rb_len) {
+ buffer->ReportOverflow(data_len - rb_len);
+ dsyslog("[audiorecorder]: buffer overflow (%s, %s())", __FILE__,
+ __func__);
+ buffer->Clear();
+ }
+}
+
+
+void cAudioReceiver::Activate(bool on)
+{
+ if (on) {
+ if (! active) {
+ active = true;
+ Start();
+ }
+ }
+ else if (active) {
+ active = false;
+ Cancel(3);
+ }
+}
+
+
+void cAudioReceiver::Action(void)
+{
+ abuffer input_buf;
+
+ dsyslog("[audiorecorder]: receiving from channel <%s> (%s, %s())",
+ channel->Name(), __FILE__, __func__);
+
+ buffer = new cRingBufferLinear(KB(100), KB(5), true, NULL);
+ postdata = new cPostData(channel);
+ rds = new cRds(postdata);
+
+ while (active) {
+ input_buf.data = buffer->Get(input_buf.length);
+ input_buf.offset = 0;
+
+ if (! input_buf.data) {
+ usleep(10000);
+ continue;
+ }
+
+
+ get_mpa_frame(&input_buf, &mpa_frame, channel->Name());
+
+ while (active && mpa_frame.data) {
+ set_recstat_rds();
+
+ if (outfile.is_open())
+ outfile.write((char *)mpa_frame.data,
+ mpa_frame.length);
+
+ input_buf.offset += mpa_frame.length;
+ get_mpa_frame(&input_buf, &mpa_frame, channel->Name());
+ }
+
+ buffer->Del(input_buf.offset);
+ }
+
+ if (outfile.is_open()) {
+ outfile.close();
+
+ if (! postdata->get_recpath().empty())
+ remove(postdata->get_recpath().c_str());
+ }
+
+
+ pes_sync = false;
+ device_number = -1;
+ recstat = recWait;
+
+ DELETE(rds);
+ DELETE(postdata);
+ DELETE(buffer);
+
+ dsyslog("[audiorecorder]: stopped receiving from channel <%s> "
+ "(%s, %s())", channel->Name(), __FILE__, __func__);
+}
+
+
+bool cAudioReceiver::is_attached(int attached_device_number)
+{
+ if (device_number < 0)
+ return false;
+
+ if (attached_device_number != device_number &&
+ attached_device_number != -1)
+ return false;
+
+ return true;
+}
+
+
+bool cAudioReceiver::is_recording(void)
+{
+ if (recstat != recStart && recstat != recRun)
+ return false;
+
+ return true;
+}
+
+
+void cAudioReceiver::set_recstat_rds(void)
+{
+ rds->put_data(mpa_frame.data, mpa_frame.length);
+
+ while(rds->set_next_frame()) {
+ recstat = rds->decode_frame();
+
+ control_track();
+ }
+}
+
+
+void cAudioReceiver::control_track(void)
+{
+//LT
+ struct stat dir;
+ std::string recdir;
+//
+ switch (recstat) {
+ case recStart:
+ if (outfile.is_open())
+ outfile.close();
+
+ postdata->start_track();
+//LT
+ recdir = cPluginAudiorecorder::get_recdir();
+ stat(recdir.c_str(), &dir);
+
+ if (! (dir.st_mode & S_IFDIR)) {
+ dsyslog("[audiorecorder]: %s (given with the parameter "
+ " --recdir|-r) isn't a directory", recdir.c_str());
+ return;
+ }
+
+ if (access(recdir.c_str(), R_OK | W_OK) != 0) {
+ dsyslog("[audiorecorder]: can't access %s (given with the "
+ "parameter --recdir|-r)", recdir.c_str());
+ return;
+ }
+//
+ outfile.open(postdata->get_recpath().c_str());
+//LT
+ if (!outfile.is_open())
+ return;
+//
+ dsyslog("[audiorecorder]: started recording track (%s) on <%s> "
+ "(%s, %s())", postdata->get_recpath().c_str(),
+ channel->Name(), __FILE__, __func__);
+ break;
+ case recStop:
+ if (outfile.is_open())
+ outfile.close();
+
+ dsyslog("[audiorecorder]: stopped recording track (%s) on <%s> "
+ "(%s, %s())", postdata->get_recpath().c_str(),
+ channel->Name(), __FILE__, __func__);
+
+ postdata->stop_track(mpa_frame.bit_rate, mpa_frame.sample_rate,
+ mpa_frame.channels, mpa_frame.length);
+
+ if (postdata->get_path().empty()) {
+ remove(postdata->get_recpath().c_str());
+ dsyslog("[audiorecorder]: track (%s) removed"
+ " (%s, %s())",
+ postdata->get_recpath().c_str(),
+ __FILE__, __func__);
+ } else {
+ cPostproc::add_track(postdata);
+ }
+ postdata->clear();
+
+ break;
+ default:
+ break;
+ }
+}
diff --git a/audioreceiver.h b/audioreceiver.h
new file mode 100644
index 0000000..33a6776
--- /dev/null
+++ b/audioreceiver.h
@@ -0,0 +1,63 @@
+/*
+ * audioreceiver.h
+ */
+
+#ifndef __AUDIORECEIVER_H
+#define __AUDIORECEIVER_H
+
+#include "postdata.h"
+#include "rds.h"
+#include "mpa-frame.h"
+#include "recstat.h"
+#include "a-tools.h"
+
+#include <vdr/receiver.h>
+#include <vdr/ringbuffer.h>
+#include <vdr/channels.h>
+
+#include <string>
+#include <fstream>
+
+
+class cAudioReceiver : public cReceiver, cThread {
+private:
+ bool active;
+ int device_number;
+
+ std::ofstream outfile;
+
+ cRingBufferLinear *buffer;
+ const cChannel *channel;
+
+ cPostData *postdata;
+ cRds *rds;
+
+ eRecStat recstat;
+ mpeg_audio_frame mpa_frame;
+ bool pes_sync;
+
+ void set_recstat_rds(void);
+ void control_track(void);
+protected:
+ virtual void Receive(uchar *data, int length);
+
+ virtual void Action(void);
+ virtual void Activate(bool on);
+public:
+ cAudioReceiver(const cChannel *_channel);
+ ~cAudioReceiver();
+
+ void set_device_number(int _device_number) { device_number =
+ _device_number; }
+ int get_device_number(void) { return device_number; }
+ bool is_attached(int attached_device_number);
+ /*
+ * returns true if the receiver is attached to the given device.
+ * if attached_device_number == -1, true is returned if the
+ * receiver is attached to a device
+ */
+ bool is_recording(void);
+ const cChannel *get_channel(void) { return channel; }
+};
+
+#endif /* __AUDIORECEIVER_H */
diff --git a/audiorecorder.c b/audiorecorder.c
new file mode 100644
index 0000000..6dd0fb4
--- /dev/null
+++ b/audiorecorder.c
@@ -0,0 +1,345 @@
+/*
+ * audiorecorder.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+
+#include "audiorecorder.h"
+#include "setup.h"
+#include "mainmenu.h"
+#include "service.h"
+
+#include <vdr/plugin.h>
+
+#include <getopt.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <iostream>
+
+extern "C" {
+ #include <avcodec.h>
+}
+
+// extern cChannels Channels;
+
+using namespace std;
+
+/* --- cPluginAudiorecorder ------------------------------------------------- */
+
+const char *cPluginAudiorecorder::DESCRIPTION = tr("floods your disc with music");
+const char *cPluginAudiorecorder::VERSION = "2.0.0_pre1";
+
+string cPluginAudiorecorder::recdir;
+int cPluginAudiorecorder::debug = 0;
+string cPluginAudiorecorder::cfg;
+string cPluginAudiorecorder::pscript;
+
+cPluginAudiorecorder::cPluginAudiorecorder(void)
+{
+ dispatcher = NULL;
+ postproc = NULL;
+}
+
+
+cPluginAudiorecorder::~cPluginAudiorecorder()
+{
+ delete dispatcher;
+ delete postproc;
+}
+
+
+const char *cPluginAudiorecorder::CommandLineHelp(void)
+{
+ return " -r dir, --recdir=dir set recording-directory to 'dir'.\n"
+ " -d level, --debug=level set debugging level (default: 0),\n"
+ " all stuff is written to stdout.\n"
+ " 0 = off\n"
+ " 1 = only errors\n"
+ " 2 = errors and further infos\n"
+ " -p script, --postproc=script set script (default: none)\n"
+ " for postprocessing.\n";
+
+}
+
+
+bool cPluginAudiorecorder::ProcessArgs(int argc, char *argv[])
+{
+ int c, option_index = 0;
+// struct stat dir;
+
+ static struct option long_options[] = {
+ { "recdir", required_argument, NULL, 'r' },
+ { "debug", required_argument, NULL, 'd' },
+ { "postproc", required_argument, NULL, 'p' },
+ { NULL }
+ };
+
+ c = getopt_long(argc, argv, "r:v:d:p:", long_options, &option_index);
+
+ while (c != -1) {
+ switch (c) {
+ case 'r':
+ recdir = optarg;
+ break;
+ case 'd':
+ if (atoi(optarg) > 0)
+ debug = atoi(optarg);
+ break;
+ case 'p':
+ pscript = optarg;
+ break;
+ default:
+ return false;
+ }
+
+ c = getopt_long(argc, argv, "r:v:d:p:", long_options,
+ &option_index);
+ }
+
+ if (recdir.empty()) {
+ cerr << endl << "audiorecorder: missing parameter --recdir|-r "
+ << endl;
+ dsyslog("audiorecorder: missing parameter --recdir|-r");
+ return false;
+ }
+
+ if (recdir[recdir.length() - 1] != '/')
+ recdir.append("/");
+//LT
+/*
+ stat(recdir.c_str(), &dir);
+
+ if (! (dir.st_mode & S_IFDIR)) {
+ cerr << endl << "audiorecorder: " << recdir << " (given with "
+ "the parameter --recdir|-r) isn't a directory" << endl;
+ dsyslog("[audiorecorder]: %s (given with the parameter "
+ " --recdir|-r) isn't a directory", recdir.c_str());
+ return false;
+ }
+
+ if (access(recdir.c_str(), R_OK | W_OK) != 0) {
+ cerr << endl << "audiorecorder: can't access " << recdir <<
+ " (given with the parameter --recdir|-r)" << endl;
+ dsyslog("[audiorecorder]: can't access %s (given with the "
+ "parameter --recdir|-r)", recdir.c_str());
+ return false;
+ }
+*/
+//
+ dsyslog("[audiorecorder]: external path-script : %s (%s, %s())", pscript.c_str(), __FILE__, __func__);
+
+
+
+ return true;
+}
+
+
+bool cPluginAudiorecorder::Initialize(void)
+{
+ /* Initialize any background activities the plugin shall perform. */
+
+ cfg.append(strdup(ConfigDirectory(PLUGIN_NAME_I18N)));
+ cfg.append("/audiorecorder.conf");
+
+ RegisterI18n(Phrases);
+
+ audio_codecs[0] = "mp2";
+ audio_codecs[1] = "libmp3lame";
+ audio_codecs[2] = "mp3";
+
+ audio_codecs_translated[0] = tr("mp2");
+ audio_codecs_translated[1] = tr("mp3");
+ audio_codecs_translated[2] = tr("mp3");
+
+ fade_types[0] = tr("off");
+ fade_types[1] = tr("linear");
+ fade_types[2] = tr("exponential");
+
+ views[0] = tr("all");
+ views[1] = tr("by artist");
+ views[2] = tr("by channel");
+ views[3] = tr("by date");
+
+ file_patterns[0] = tr("Artist/Title");
+ file_patterns[1] = tr("Artist - Title");
+ file_patterns[2] = tr("Channel/Artist/Title");
+ file_patterns[3] = tr("Channel - Artist - Title");
+
+ return true;
+}
+
+
+bool cPluginAudiorecorder::Start(void)
+{
+ /* initialize libavcodec */
+ avcodec_init();
+ avcodec_register_all();
+
+ probe_audio_codecs();
+
+ Cache.load();
+
+ dispatcher = new cDispatcher();
+ postproc = new cPostproc();
+
+ return true;
+}
+
+
+void cPluginAudiorecorder::Stop(void)
+{
+ /* Stop any background activities the plugin shall perform. */
+}
+
+
+void cPluginAudiorecorder::Housekeeping(void)
+{
+ /* Perform any cleanup or other regular tasks. */
+}
+
+
+const char *cPluginAudiorecorder::MainMenuEntry(void)
+{
+ main_menu.str("");
+ main_menu.clear();
+
+ main_menu << tr("Audiorecorder") << " ("
+ << (dispatcher->is_active() ? tr("on") : tr("off"))
+ << ") " << dispatcher->get_recording_receivers()
+ << "/" << dispatcher->get_attached_receivers(-1)
+ << "/" << SetupValues.max_receivers
+ << ", " << postproc->get_num_queued();
+
+ return main_menu.str().c_str();
+}
+
+
+cOsdObject *cPluginAudiorecorder::MainMenuAction(void)
+{
+ return new cMainmenu(dispatcher);
+}
+
+
+cString cPluginAudiorecorder::Active(void)
+{
+ /*
+ if (postproc->get_num_queued() != 0)
+ return "active postprocessings";
+ */
+ return NULL;
+}
+
+
+cMenuSetupPage *cPluginAudiorecorder::SetupMenu(void)
+{
+ return new cAudiorecorderSetup();
+}
+
+
+bool cPluginAudiorecorder::SetupParse(const char *name, const char *value)
+{
+ if (strcmp(name, "start_type") == 0)
+ SetupValues.start_type = atoi(value);
+ else if (strcmp(name, "max_receivers") == 0)
+ SetupValues.max_receivers = atoi(value);
+ else if (strcmp(name, "min_free_space") == 0)
+ SetupValues.min_free_space = atoi(value);
+ else if (strcmp(name, "pause") == 0)
+ SetupValues.pause = atoi(value);
+ else if (strcmp(name, "max_postproc") == 0)
+ SetupValues.max_postproc = atoi(value);
+ else if (strcmp(name, "default_view") == 0)
+ SetupValues.default_view = atoi(value);
+ else if (strcmp(name, "fade_in") == 0)
+ SetupValues.fade_in = atoi(value);
+ else if (strcmp(name, "fade_in_mode") == 0)
+ SetupValues.fade_in_mode = atoi(value);
+ else if (strcmp(name, "fade_out") == 0)
+ SetupValues.fade_out = atoi(value);
+ else if (strcmp(name, "fade_out_mode") == 0)
+ SetupValues.fade_out_mode = atoi(value);
+ else if (strcmp(name, "audio_codec") == 0) {
+ int c;
+
+ for (c = 0; c < SetupValues.num_audio_codecs; ++c) {
+ if (strcmp(value, audio_codecs_translated[c]) == 0) {
+ SetupValues.audio_codec = c;
+ break;
+ }
+ }
+ }
+ else if (strcmp(name, "bit_rate") == 0)
+ SetupValues.bit_rate = atoi(value);
+ else if (strcmp(name, "file_pattern") == 0)
+ SetupValues.file_pattern = atoi(value);
+ else if (strcmp(name, "upper") == 0)
+ SetupValues.upper = atoi(value);
+ else if (strcmp(name, "copies") == 0)
+ SetupValues.copies = atoi(value);
+ else
+ return false;
+
+ return true;
+}
+
+
+bool cPluginAudiorecorder::Service(const char *id, void *data)
+{
+ if (! id)
+ return false;
+
+ if (strcmp(id, "Audiorecorder-StatusRtpChannel-v1.0") == 0) {
+ if (data) {
+ struct Audiorecorder_StatusRtpChannel_v1_0 *d;
+ d = (struct Audiorecorder_StatusRtpChannel_v1_0 *)data;
+ d->status =
+ dispatcher->get_recording_status(d->channel);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+const char **cPluginAudiorecorder::SVDRPHelpPages(void)
+{
+ /* Return help text for SVDRP commands this plugin implements */
+ return NULL;
+}
+
+
+cString cPluginAudiorecorder::SVDRPCommand(const char *command,
+ const char *option, int &reply_code)
+{
+ /* Process SVDRP commands this plugin implements */
+ return NULL;
+}
+
+
+void cPluginAudiorecorder::probe_audio_codecs() {
+ int c;
+ const char *tmp;
+ AVCodec *codec = NULL;
+
+ for (c = 1; c < SetupValues.num_audio_codecs; ++c) {
+ codec = avcodec_find_encoder_by_name(audio_codecs[c]);
+ if (codec)
+ continue;
+
+ dsyslog("[audiorecorder]: your version of libavcodec (ffmpeg) "
+ "is not compiled with %s support (%s, %s())", audio_codecs[c], __FILE__, __func__);
+
+ tmp = audio_codecs[c];
+ audio_codecs[c] =
+ audio_codecs[SetupValues.num_audio_codecs - 1];
+ audio_codecs[SetupValues.num_audio_codecs - 1] = tmp;
+ --SetupValues.num_audio_codecs;
+ }
+}
+
+
+VDRPLUGINCREATOR(cPluginAudiorecorder); /* Don't touch this! */
diff --git a/audiorecorder.h b/audiorecorder.h
new file mode 100644
index 0000000..ce49c76
--- /dev/null
+++ b/audiorecorder.h
@@ -0,0 +1,63 @@
+/*
+ * audiorecorder.h
+ */
+
+#ifndef __AUDIORECORDER_H
+#define __AUDIORECORDER_H
+
+#include "dispatcher.h"
+#include "postproc.h"
+
+#include <vdr/plugin.h>
+
+#include <sstream>
+#include <string>
+
+#include "audiorecorder_i18n.h"
+
+class cPluginAudiorecorder : public cPlugin {
+private:
+ cDispatcher *dispatcher;
+ cPostproc *postproc;
+
+ static const char *DESCRIPTION;
+ static const char *VERSION;
+
+ static std::string recdir;
+ static int debug;
+
+ static std::string cfg;
+ static std::string pscript;
+
+ std::stringstream main_menu;
+
+ void probe_audio_codecs(void);
+public:
+ cPluginAudiorecorder(void);
+ virtual ~cPluginAudiorecorder();
+ virtual const char *Version(void) { return VERSION; }
+ virtual const char *Description(void) { return DESCRIPTION; }
+ virtual const char *CommandLineHelp(void);
+ virtual bool ProcessArgs(int argc, char *argv[]);
+ virtual bool Initialize(void);
+ virtual bool Start(void);
+ virtual void Stop(void);
+ virtual void Housekeeping(void);
+ virtual const char *MainMenuEntry(void);
+ virtual cOsdObject *MainMenuAction(void);
+ virtual cString Active(void);
+ virtual cMenuSetupPage *SetupMenu(void);
+ virtual bool SetupParse(const char *name, const char *value);
+ virtual bool Service(const char *id, void *data = NULL);
+ virtual const char **SVDRPHelpPages(void);
+ virtual cString SVDRPCommand(const char *command, const char *option,
+ int &reply_code);
+
+ static const char *get_version(void) { return VERSION; }
+ static std::string get_recdir(void) { return recdir; }
+ static int get_dbg_level(void) { return debug; }
+ static std::string get_cfg(void) { return cfg; }
+ static std::string get_pscript(void) { return pscript; }
+};
+
+#endif /* __AUDIORECORDER_H */
diff --git a/browse-item.c b/browse-item.c
new file mode 100644
index 0000000..5b59923
--- /dev/null
+++ b/browse-item.c
@@ -0,0 +1,69 @@
+/*
+ * browse-item.c
+ */
+
+#include "browse-item.h"
+
+#include <string>
+
+
+using namespace std;
+
+/* --- cBrowseItem ---------------------------------------------------------- */
+
+cBrowseItem::cBrowseItem(cBrowseItem *_main_item, const cTrackInfo *_track,
+ int _column, eItemType _type)
+:cOsdItem()
+{
+ main_item = _main_item;
+ if (main_item)
+ main_item->increase_items();
+
+ track = _track;
+ column = _column;
+ type = _type;
+
+ items = 0;
+}
+
+
+void cBrowseItem::increase_items(void)
+{
+ ++items;
+
+ if (main_item)
+ main_item->increase_items();
+}
+
+
+void cBrowseItem::delete_items(int del_items)
+{
+ items -= del_items;
+
+ if (main_item)
+ main_item->delete_items(del_items);
+
+ if (items < 0)
+ items = 0;
+}
+
+
+void cBrowseItem::toggle_node(void)
+{
+ string txt = Text();
+
+ if (type == itemNodeOpen) {
+ type = itemNodeClose;
+ string::size_type f = txt.find_first_of("-");
+ if (f != string::npos)
+ txt.replace(f, 1, "+");
+ }
+ else if (type == itemNodeClose) {
+ type = itemNodeOpen;
+ string::size_type f = txt.find_first_of("+");
+ if (f != string::npos)
+ txt.replace(f, 1, "-");
+ }
+
+ SetText(txt.c_str());
+}
diff --git a/browse-item.h b/browse-item.h
new file mode 100644
index 0000000..9da3452
--- /dev/null
+++ b/browse-item.h
@@ -0,0 +1,44 @@
+/*
+ * browse-item.h
+ */
+
+#ifndef __BROWSE_ITEM_H
+#define __BROWSE_ITEM_H
+
+#include "trackinfo.h"
+
+#include <vdr/osdbase.h>
+
+
+enum eItemType {
+ itemTrack,
+ itemNodeOpen,
+ itemNodeClose,
+};
+
+class cBrowseItem : public cOsdItem {
+private:
+ const cTrackInfo *track;
+ cBrowseItem *main_item;
+ int column;
+ eItemType type;
+ int items;
+public:
+ cBrowseItem(cBrowseItem *_main_item, const cTrackInfo *_track,
+ int _column, eItemType _type);
+
+ void increase_items(void);
+ void delete_items(int del_items);
+ void toggle_node(void);
+
+ const cTrackInfo *get_track(void) const { return track; }
+ const cBrowseItem *get_main_item(void) const { return main_item; }
+ int get_column(void) const { return column; }
+ int get_items(void) const { return items; }
+
+ bool is_node(void) const { return (type != itemTrack); }
+ bool is_open(void) const { return (type == itemNodeOpen); }
+ bool is_closed(void) const { return (type == itemNodeClose); }
+};
+
+#endif /* __BROWSE_ITEM__H */
diff --git a/browse.c b/browse.c
new file mode 100644
index 0000000..0e7bac3
--- /dev/null
+++ b/browse.c
@@ -0,0 +1,638 @@
+/*
+ * browse.c
+ */
+
+#include "browse.h"
+#include "cache.h"
+#include "external-player.h"
+#include "audiorecorder.h"
+#include "setup.h"
+
+#include <vdr/skins.h>
+#include <vdr/interface.h>
+#include <vdr/tools.h>
+
+#include <sstream>
+
+
+#define MAXCOLS 5
+#define TAB_LEN 3
+#define SERVICE "MP3-Play-v1"
+
+using namespace std;
+
+/* --- cBrowse -------------------------------------------------------------- */
+
+cBrowse::cBrowse(void)
+:cOsdMenu("")
+{
+ num_columns = MAXCOLS;
+ columns = NULL;
+ columns = new cColumn[num_columns + 1];
+
+ player = cPluginManager::CallFirstService(SERVICE);
+
+ string skin = Skins.Current()->Name();
+ width = DisplayMenu()->EditableWidth();
+
+ if (skin != "curses")
+ width /= 12;
+
+ dsyslog("[audiorecorder]: skin %s (width %d) detected (%s, %s())",
+ skin.c_str(), width, __FILE__, __func__);
+
+ set_view(SetupValues.default_view);
+}
+
+
+cBrowse::~cBrowse()
+{
+ delete[] columns;
+}
+
+
+eOSState cBrowse::ProcessKey(eKeys key)
+{
+ eOSState state = cOsdMenu::ProcessKey(key);
+
+ if (HasSubMenu()) {
+ process_submenu_states(state);
+
+ if (state == osBack || key == kBlue || key == kOk)
+ CloseSubMenu();
+
+ return osContinue;
+ }
+
+ set_status();
+ set_help_keys();
+
+ cBrowseItem *item = get_actual_item();
+
+ switch (key) {
+ case kRed:
+ if (! item)
+ break;
+
+ if (item->is_node())
+ AddSubMenu(new cBrowseAction(true,
+ get_category(item)));
+ else
+ AddSubMenu(new cBrowseAction(false,
+ get_trackname(item->get_track())));
+
+ break;
+ case kGreen:
+ if (help_green.empty())
+ break;
+
+ expand ? expand = false : expand = true;
+ set_view(view, false);
+
+ break;
+ case kYellow:
+ set_view(++view);
+
+ break;
+ case kBlue:
+ if (item && ! item->is_node())
+ AddSubMenu(new cBrowseInfo(item->get_track()));
+
+ break;
+ case kOk:
+ if (! item)
+ break;
+
+ if (! item->is_node())
+ play_file(item->get_track());
+ else if (item->is_closed()) {
+ item->toggle_node();
+ insert_items();
+ }
+ else {
+ item->toggle_node();
+ delete_items();
+ }
+ break;
+ default:
+ break;
+ }
+
+ return state;
+}
+
+
+void cBrowse::set_view(int _view, bool init)
+{
+ view = _view;
+
+ if (view == 1) {
+ /* view by artist */
+ num_columns = 2;
+
+ columns[0].set(colArtist, TAB_LEN);
+ columns[1].set(colTitle, width - TAB_LEN);
+ columns[2].set(colEnd);
+
+ if (init)
+ expand = false;
+
+ help_yellow = views[2];
+ }
+ else if (view == 2) {
+ /* view by channel */
+ num_columns = 3;
+
+ columns[0].set(colChannel, TAB_LEN);
+ columns[1].set(colEvent, TAB_LEN);
+ columns[2].set(colArtistTitle, width - 2 * TAB_LEN);
+ columns[3].set(colEnd);
+
+ if (init)
+ expand = false;
+
+ help_yellow = views[3];
+ }
+ else if (view == 3) {
+ /* view by date */
+ num_columns = 2;
+
+ columns[0].set(colDate, TAB_LEN);
+ columns[1].set(colTime, 6, true);
+ columns[2].set(colChannel, 11, true, true);
+ columns[3].set(colArtistTitle, width - TAB_LEN - 17);
+ columns[4].set(colEnd);
+
+ if (init)
+ expand = true;
+
+ help_yellow = views[0];
+ }
+ else {
+ /* all */
+ view = 0;
+ num_columns = 1;
+
+ columns[0].set(colArtistTitle, width);
+ columns[1].set(colEnd);
+
+ if (init)
+ expand = true;
+
+ help_yellow = views[1];
+ }
+
+ if (init) {
+ SetCols(columns[0].get_width(), columns[1].get_width(),
+ columns[2].get_width(), columns[3].get_width(),
+ columns[4].get_width());
+
+ Cache.sort(columns);
+ }
+
+ Clear();
+ insert_items();
+
+ set_status();
+ set_help_keys();
+ set_title();
+}
+
+
+void cBrowse::set_help_keys(void)
+{
+ /* green key */
+ if (view == 0)
+ help_green.erase();
+ else if (expand)
+ help_green = tr("collapse all");
+ else
+ help_green = tr("expand all");
+
+ cBrowseItem *item = get_actual_item();
+
+ SetHelp(tr("action"), (help_green.empty() ? NULL : help_green.c_str()) ,
+ help_yellow, (item && ! item->is_node() ? "info" : NULL));
+}
+
+
+void cBrowse::set_status(void)
+{
+ /* disable status for the skin sttng: */
+ if (strcmp(Skins.Current()->Name(), "sttng") == 0)
+ return;
+
+ string status(views[view]);
+
+ cBrowseItem *item = get_actual_item();
+
+ if (item && item->get_main_item()) {
+ status.append(" ");
+ status.append(get_category(item));
+ }
+
+ cut_string(status, width);
+ SetStatus(status.c_str());
+}
+
+
+void cBrowse::set_title(void)
+{
+ stringstream title;
+ title << tr("Audiorecorder") << ", " << tr("Browse tracks") << " (" << Cache.get_num_cached()
+ << ")";
+
+ SetTitle(title.str().c_str());
+}
+
+
+void cBrowse::process_submenu_states(eOSState &state)
+{
+ cBrowseItem *item = get_actual_item();
+
+ switch(state) {
+ case osUser1:
+ play_all_files(item);
+ state = osBack;
+
+ break;
+ case osUser6:
+ if (item)
+ play_file(item->get_track());
+
+ state = osBack;
+
+ break;
+ default:
+ break;
+ }
+}
+
+
+cBrowseItem *cBrowse::get_actual_item(void)
+{
+ if (Current() < 0)
+ return NULL;
+
+ return (cBrowseItem *)Get(Current());
+}
+
+
+void cBrowse::insert_items(void)
+{
+ int pos = Current();
+
+ /* clean up old states */
+ for (int c = 0; c < num_columns; ++c) {
+ columns[c].set_main_item(NULL);
+ columns[c].del_last_entry();
+ }
+
+ int column = 0;
+ cBrowseItem *item = get_actual_item();
+ if (item && item->is_node()) {
+ set_filter(item);
+ column = item->get_column();
+ columns[column].set_main_item(item);
+ ++column;
+ }
+
+ int depth;
+ if (expand)
+ depth = num_columns;
+ else
+ depth = column + 1;
+
+ for (const cTrackInfo *track = Cache.get_next_track(true); track;
+ track = Cache.get_next_track()) {
+
+ if (filter_track(track))
+ continue;
+
+ for (int c = column; c < depth; ++c) {
+ eItemType type;
+
+ string entry = get_value_of_column(track, c);
+
+ if (c == num_columns - 1)
+ type = itemTrack;
+ else if (expand)
+ type = itemNodeOpen;
+ else
+ type = itemNodeClose;
+
+ /* don't show double nodes: */
+ if (type != itemTrack &&
+ entry == columns[c].get_last_entry())
+ continue;
+
+ columns[c].set_last_entry(entry);
+ for (int i = c + 1; i < num_columns; ++i)
+ columns[i].del_last_entry();
+
+ if (type == itemNodeOpen)
+ entry.insert(0, "[-] ");
+ else if (type == itemNodeClose)
+ entry.insert(0, "[+] ");
+
+ string indent(c, '\t');
+ entry.insert(0, indent);
+
+ cBrowseItem *item, *main = NULL;
+ if (c > 0)
+ main = columns[c - 1].get_main_item();
+
+ item = new cBrowseItem(main, track, c, type);
+ item->SetText(entry.c_str());
+ columns[c].set_main_item(item);
+
+ Add(item, true, Get(Current()));
+ }
+ }
+
+ SetCurrent(Get(pos));
+ Display();
+}
+
+
+void cBrowse::delete_items(void)
+{
+ cBrowseItem *item = get_actual_item();
+ if (! item || ! item->is_node())
+ return;
+
+ for (int c = 0; c < item->get_items(); ++c)
+ Del(Current() + 1);
+
+ item->delete_items(item->get_items());
+
+ Display();
+}
+
+
+void cBrowse::set_filter(const cBrowseItem *item)
+{
+ for (int c = 0; c < num_columns; ++c) {
+ string filter("");
+ if (c <= item->get_column())
+ filter = get_value_of_column(item->get_track(), c);
+
+ columns[c].set_filter(filter);
+ }
+}
+
+
+bool cBrowse::filter_track(const cTrackInfo *track)
+{
+ for (int c = 0; c < num_columns; ++c) {
+
+ if (columns[c].get_filter().empty())
+ continue;
+
+ if (columns[c].get_filter() != get_value_of_column(track, c))
+ return true;
+ }
+
+ return false;
+}
+
+
+string cBrowse::get_category(const cBrowseItem *item)
+{
+ string category("");
+
+ if (item) {
+ int columns = item->get_column();
+
+ if (item->is_node())
+ ++columns;
+
+ for (int c = 0; c < columns; ++c) {
+ if (c != 0)
+ category.append("/");
+
+ category.append(get_value_of_column(item->get_track(),
+ c));
+ }
+ }
+
+ return category;
+}
+
+
+string cBrowse::get_trackname(const cTrackInfo *track)
+{
+ string trackstr("");
+
+ if (track) {
+ trackstr.append(track->get_artist());
+ trackstr.append(" - ");
+ trackstr.append(track->get_title());
+ }
+
+ return trackstr;
+}
+
+
+string cBrowse::get_value_of_column(const cTrackInfo *track, int c)
+{
+ string value("");
+ bool join = true;
+
+ while (join && columns[c].get_type() != colEnd) {
+ stringstream col_tmp;
+
+ switch (columns[c].get_type()) {
+ case colArtist:
+ col_tmp << track->get_artist();
+ break;
+ case colTitle:
+ col_tmp << track->get_title();
+ break;
+ case colArtistTitle:
+ col_tmp << track->get_artist() << " - "
+ << track->get_title();
+ break;
+ case colAlbum:
+ col_tmp << track->get_album();
+ break;
+ case colTrack:
+ col_tmp << track->get_track();
+ break;
+ case colYear:
+ col_tmp << track->get_year();
+ break;
+ case colGenre:
+ col_tmp << track->get_genre();
+ break;
+ case colChannel:
+ col_tmp << track->get_channel();
+ break;
+ case colEvent:
+ col_tmp << track->get_event();
+ break;
+ case colDate:
+ col_tmp << track->get_date();
+ break;
+ case colTime:
+ col_tmp << track->get_time();
+ break;
+ default:
+ break;
+ }
+
+ if (col_tmp.str().empty())
+ col_tmp << tr("unknown");
+
+ string col(col_tmp.str());
+
+ if (columns[c].get_cut())
+ cut_string(col, columns[c].get_width() - 1);
+
+ value.append(col);
+
+ join = columns[c].is_joined();
+ if (join)
+ value.append("\t");
+
+ ++c;
+ }
+
+ return value;
+}
+
+
+void cBrowse::play_all_files(const cBrowseItem *node)
+{
+ if (! player) {
+ Skins.Message(mtError, tr("No external player-plugin found"), 2);
+ return;
+ }
+
+ if (! node || ! node->is_node())
+ return;
+
+ set_filter(node);
+
+ string status = tr("Playing all tracks in ");
+ status.append(get_category(node));
+ cut_string(status, width);
+ SetStatus(status.c_str());
+
+ for (const cTrackInfo *track = Cache.get_next_track(true); track;
+ track = Cache.get_next_track()) {
+
+ if (! filter_track(track))
+ play_file(track, false);
+ }
+}
+
+
+void cBrowse::play_file(const cTrackInfo *track, bool set_status)
+{
+ if (! player) {
+ Skins.Message(mtError, tr("No external player-plugin found"), 2);
+ return;
+ }
+
+ MP3ServiceData data;
+ data.data.filename = track->get_path().c_str();
+ data.result = 0;
+
+#ifndef AUDIORECORDER_DEVEL
+ player->Service(SERVICE, &data);
+
+ if (data.result == 0) {
+ dsyslog("[audiorecorder]: plugin %s could not play the file "
+ "%s (%s, %s())", player->Name(),
+ track->get_path().c_str(), __FILE__, __func__);
+ return;
+ }
+#endif /* AUDIORECORDER_DEVEL */
+
+ if (set_status) {
+ string status = tr("Playing ");
+ status.append(get_trackname(track));
+ cut_string(status, width);
+ SetStatus(status.c_str());
+ }
+}
+
+
+void cBrowse::cut_string(string &cut, int length)
+{
+ if ((int)cut.length() > length) {
+ cut.erase(length - 1);
+ cut.append("~");
+ }
+}
+
+
+/* --- cBrowseAction -------------------------------------------------------- */
+
+cBrowseAction::cBrowseAction(bool node, const string &text)
+:cOsdMenu(tr("Audiorecorder, Action"))
+{
+ string status("");
+ if (node) {
+ status.append(tr("Category : "));
+ status.append(text);
+ Add(new cOsdItem(tr("Play all tracks"), osUser1));
+ Add(new cOsdItem(tr("More to come ..."), osUser7));
+ }
+ else {
+ status.append(tr("Track: "));
+ status.append(text);
+ Add(new cOsdItem(tr("Play track"), osUser6));
+ Add(new cOsdItem(tr("More to come ..."), osUser7));
+ }
+
+ SetStatus(status.c_str());
+ SetHelp(NULL, NULL, NULL, tr("back"));
+}
+
+
+/* --- cBrowseInfo ---------------------------------------------------------- */
+
+cBrowseInfo::cBrowseInfo(const cTrackInfo *track)
+:cOsdMenu(tr("Audiorecorder, Info"), 11)
+{
+ string path = track->get_path();
+ path.erase(0, cPluginAudiorecorder::get_recdir().length());
+ Add(add_item(tr("File"), path));
+
+ Add(add_item(tr("Artist"), track->get_artist()));
+ Add(add_item(tr("Title"), track->get_title()));
+ Add(add_item(tr("Album"), track->get_album()));
+ Add(add_item(tr("Genre"), track->get_genre()));
+
+ stringstream tmp;
+ tmp << track->get_track();
+ Add(add_item(tr("Track"), tmp.str()));
+
+ tmp.str("");
+ tmp.clear();
+ tmp << track->get_year();
+ Add(add_item(tr("Year"), tmp.str()));
+
+ Add(add_item(tr("Channel"), track->get_channel()));
+ Add(add_item(tr("Event"), track->get_event()));
+ Add(add_item(tr("Date"), track->get_date()));
+ Add(add_item(tr("Time"), track->get_time()));
+
+ SetHelp(NULL, NULL, NULL, tr("back"));
+}
+
+
+cOsdItem *cBrowseInfo::add_item(const char *type, const string &text)
+{
+ string txt = type;
+ txt.append(":\t");
+
+ if (text.empty() || text == "0")
+ txt.append(tr("unknown"));
+ else
+ txt.append(text);
+
+ return new cOsdItem(txt.c_str(), osUnknown, false);
+}
diff --git a/browse.h b/browse.h
new file mode 100644
index 0000000..0789a72
--- /dev/null
+++ b/browse.h
@@ -0,0 +1,71 @@
+/*
+ * browse.h
+ */
+
+#ifndef __BROWSE_H
+#define __BROWSE_H
+
+#include "column.h"
+#include "trackinfo.h"
+#include "browse-item.h"
+
+#include <vdr/osdbase.h>
+#include <vdr/plugin.h>
+
+#include <string>
+
+
+class cBrowse : public cOsdMenu {
+private:
+ const char *help_yellow;
+ std::string help_green;
+ int view, num_columns, width;
+ bool expand;
+ cColumn *columns;
+ cPlugin *player;
+
+ void set_view(int _view, bool init = true);
+ void set_help_keys(void);
+ void set_status(void);
+ void set_title(void);
+
+ void process_submenu_states(eOSState &state);
+
+ cBrowseItem *get_actual_item(void);
+ void insert_items(void);
+ void delete_items(void);
+
+ void set_filter(const cBrowseItem *item);
+
+ bool filter_track(const cTrackInfo *track);
+
+ std::string get_category(const cBrowseItem *item);
+ std::string get_trackname(const cTrackInfo *track);
+ std::string get_value_of_column(const cTrackInfo *track, int c);
+
+ void play_all_files(const cBrowseItem *node);
+ void play_file(const cTrackInfo *track, bool set_status = true);
+
+ void cut_string(std::string &cut, int length);
+public:
+ cBrowse(void);
+ ~cBrowse();
+
+ virtual eOSState ProcessKey(eKeys key);
+};
+
+
+class cBrowseAction : public cOsdMenu {
+public:
+ cBrowseAction(bool node, const std::string &text);
+};
+
+
+class cBrowseInfo : public cOsdMenu {
+private:
+ cOsdItem *add_item(const char *type, const std::string &text);
+public:
+ cBrowseInfo(const cTrackInfo *track);
+};
+
+#endif /* __BROWSE__H */
diff --git a/cache.c b/cache.c
new file mode 100644
index 0000000..66f1bef
--- /dev/null
+++ b/cache.c
@@ -0,0 +1,232 @@
+/*
+ * cache.c
+ */
+
+#include "cache.h"
+#include "audiorecorder.h"
+
+#include <vdr/skins.h>
+#include <vdr/tools.h>
+
+
+#define CACHEFILE "cache.xml"
+
+using namespace std;
+
+cCache Cache;
+
+/* --- cCache --------------------------------------------------------------- */
+
+const cColumn *cCache::sort_order = NULL;
+
+cCache::cCache()
+:cThread()
+{
+ what_to_do = 0;
+ active = false;
+}
+
+
+cCache::~cCache()
+{
+ Activate(false);
+}
+
+
+void cCache::Activate(bool on)
+{
+ if (on) {
+ if (! active) {
+ active = true;
+ Start();
+ }
+ }
+ else if (active) {
+ active = false;
+ Cancel(0);
+ }
+}
+
+
+void cCache::Action(void)
+{
+ if (what_to_do & REBUILD) {
+ dsyslog("[audiorecorder]: rebuilding cache thread started (%s, %s())",
+ __FILE__, __func__);
+
+ Lock();
+ tracklist.clear();
+ xmlcache.rebuild();
+ Unlock();
+
+ Skins.QueueMessage(mtInfo, tr("Audiorecorder: rebuilding cache finished"));
+ dsyslog("[audiorecorder]: rebuilding cache thread finished (%s, %s())",
+ __FILE__, __func__);
+
+ what_to_do ^= REBUILD;
+
+ active = false;
+ } else if (what_to_do & LOAD) {
+ string path = cPluginAudiorecorder::get_recdir();
+ path.append(CACHEFILE);
+
+ Lock();
+ xmlcache.load(path);
+ Unlock();
+ Skins.QueueMessage(mtInfo, tr("Audiorecorder: loading of cache finished"));
+ dsyslog("[audiorecorder]: loading of cache finished (%s, %s())",
+ __FILE__, __func__);
+
+ what_to_do ^= LOAD;
+ active = false;
+ }
+}
+
+/*
+void cCache::load()
+{
+ string path = cPluginAudiorecorder::get_recdir();
+ path.append(CACHEFILE);
+
+ Lock();
+ xmlcache.load(path);
+ Unlock();
+}
+*/
+
+void cCache::sort(const cColumn *columns)
+{
+ if (! columns)
+ return;
+
+ sort_order = columns;
+
+ Lock();
+ tracklist.sort(cCache::sort_tracklist);
+ Unlock();
+}
+
+
+void cCache::add_track(const cTrackInfo &trackinfo, bool add_xmlcache)
+{
+ if (trackinfo.get_artist().empty() || trackinfo.get_title().empty())
+ return;
+
+ Lock();
+ tracklist.push_back(trackinfo);
+
+ if (add_xmlcache)
+ xmlcache.add_track(trackinfo);
+ Unlock();
+}
+
+
+int cCache::get_num_cached(void)
+{
+ int num;
+ Lock();
+ num = tracklist.size();
+ Unlock();
+
+ return num;
+}
+
+
+const cTrackInfo *cCache::get_next_track(bool reset)
+{
+ if (reset)
+ track = tracklist.begin();
+
+ if (track == tracklist.end())
+ return NULL;
+
+ cTrackInfo *trackinfo = &(*track);
+ ++track;
+
+ return trackinfo;
+}
+
+
+bool cCache::sort_tracklist(const cTrackInfo &lhs, const cTrackInfo &rhs)
+{
+ for (int c = 0; sort_order[c].get_type() != colEnd; ++c) {
+ switch(sort_order[c].get_type()) {
+ case colArtist:
+ if (lhs.get_artist() > rhs.get_artist())
+ return false;
+ if (lhs.get_artist() < rhs.get_artist())
+ return true;
+ break;
+ case colTitle:
+ if (lhs.get_title() > rhs.get_title())
+ return false;
+ if (lhs.get_title() < rhs.get_title())
+ return true;
+ break;
+ case colArtistTitle:
+ if (lhs.get_artist() > rhs.get_artist())
+ return false;
+ if (lhs.get_artist() < rhs.get_artist())
+ return true;
+ if (lhs.get_title() > rhs.get_title())
+ return false;
+ if (lhs.get_title() < rhs.get_title())
+ return true;
+ break;
+ case colAlbum:
+ if (lhs.get_album() > rhs.get_album())
+ return false;
+ if (lhs.get_album() < rhs.get_album())
+ return true;
+ break;
+ case colTrack:
+ if (lhs.get_track() > rhs.get_track())
+ return false;
+ if (lhs.get_track() < rhs.get_track())
+ return true;
+ break;
+ case colYear:
+ if (lhs.get_year() > rhs.get_year())
+ return false;
+ if (lhs.get_year() < rhs.get_year())
+ return true;
+ break;
+ case colGenre:
+ if (lhs.get_genre() > rhs.get_genre())
+ return false;
+ if (lhs.get_genre() < rhs.get_genre())
+ return true;
+ break;
+ case colChannel:
+ if (lhs.get_channel() > rhs.get_channel())
+ return false;
+ if (lhs.get_channel() < rhs.get_channel())
+ return true;
+ break;
+ case colEvent:
+ if (lhs.get_event() > rhs.get_event())
+ return false;
+ if (lhs.get_event() < rhs.get_event())
+ return true;
+ break;
+ case colDate:
+ /* up-to-date date's first */
+ if (lhs.get_date() > rhs.get_date())
+ return true;
+ if (lhs.get_date() < rhs.get_date())
+ return false;
+ break;
+ case colTime:
+ /* up-to-date time's first */
+ if (lhs.get_time() > rhs.get_time())
+ return true;
+ if (lhs.get_time() < rhs.get_time())
+ return false;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
diff --git a/cache.h b/cache.h
new file mode 100644
index 0000000..75023ea
--- /dev/null
+++ b/cache.h
@@ -0,0 +1,55 @@
+/*
+ * cache.h
+ */
+
+#ifndef __CACHE_H
+#define __CACHE_H
+
+#include "xml-cache.h"
+#include "trackinfo.h"
+#include "column.h"
+
+#include <vdr/thread.h>
+
+#include <list>
+#include <string>
+
+#define NOTHING 0
+#define LOAD 1
+#define REBUILD 2
+
+
+class cCache : public cThread {
+private:
+ bool active;
+ int what_to_do;
+
+ std::list<cTrackInfo> tracklist;
+ std::list<cTrackInfo>::iterator track;
+
+ cXmlCache xmlcache;
+
+ static const cColumn *sort_order;
+
+ static bool sort_tracklist(const cTrackInfo & lhs,
+ const cTrackInfo &rhs);
+protected:
+ virtual void Action(void);
+ virtual void Activate(bool on);
+public:
+ cCache(void);
+ ~cCache();
+ void load(void) { what_to_do ^= LOAD; Activate(true); }
+ void rebuild(void) { what_to_do ^= REBUILD; Activate(true); }
+ bool is_rebuilding(void) { return active; }
+ void sort(const cColumn *columns);
+
+ void add_track(const cTrackInfo &trackinfo, bool add_xmlcache = true);
+ int get_num_cached(void);
+
+ const cTrackInfo *get_next_track(bool reset = false);
+};
+
+extern cCache Cache;
+
+#endif /* __CACHE_H */
diff --git a/column.c b/column.c
new file mode 100644
index 0000000..cd2ec84
--- /dev/null
+++ b/column.c
@@ -0,0 +1,29 @@
+/*
+ * column.c
+ */
+
+#include "column.h"
+
+
+/* --- cColumn -------------------------------------------------------------- */
+
+cColumn::cColumn(void)
+{
+ set(colEnd);
+}
+
+
+void cColumn::set(eColumn _type, int _width, bool _join, bool _cut)
+{
+ type = _type;
+ width = _width;
+ if (width < 0)
+ width = 0;
+
+ join = _join;
+ cut = _cut;
+
+ filter.erase();
+ last_entry.erase();
+ item = NULL;
+}
diff --git a/column.h b/column.h
new file mode 100644
index 0000000..4493814
--- /dev/null
+++ b/column.h
@@ -0,0 +1,58 @@
+/*
+ * column.h
+ */
+
+#ifndef __COLUMN_H
+#define __COLUMN_H
+
+#include "browse-item.h"
+
+#include <string>
+
+
+enum eColumn {
+ colEnd,
+ colArtist,
+ colTitle,
+ colArtistTitle,
+ colAlbum,
+ colTrack,
+ colYear,
+ colGenre,
+ colChannel,
+ colEvent,
+ colDate,
+ colTime
+};
+
+
+class cColumn {
+private:
+ eColumn type;
+ int width;
+ bool join;
+ bool cut;
+ std::string filter, last_entry;
+ cBrowseItem *item;
+public:
+ cColumn(void);
+
+ void set(eColumn _type, int _width = 0, bool _join = false,
+ bool _cut = false);
+
+ void set_filter(std::string &_filter) { filter = _filter; }
+ void set_last_entry(std::string &_last_entry)
+ { last_entry = _last_entry; }
+ void del_last_entry(void) { last_entry.erase(); }
+ void set_main_item(cBrowseItem *_item) { item = _item; }
+
+ eColumn get_type(void) const { return type; }
+ int get_width(void) const { return width; }
+ bool is_joined(void) const { return join; }
+ bool get_cut(void) const { return cut; }
+ const std::string &get_filter(void) const { return filter; }
+ const std::string &get_last_entry(void) const { return last_entry; }
+ cBrowseItem *get_main_item(void) const { return item; }
+};
+
+#endif /* __COLUMN_H */
diff --git a/contrib/audiorecorder.conf b/contrib/audiorecorder.conf
new file mode 100644
index 0000000..548fe26
--- /dev/null
+++ b/contrib/audiorecorder.conf
@@ -0,0 +1,12 @@
+S19.2E-1-1093-28406 /* DAS MODUL */
+S19.2E-1-1093-28475 /* Eins Live */
+S19.2E-1-1093-28476 /* WDR 2 */
+S19.2E-1-1093-28468 /* SWR 3 */
+S19.2E-1-1093-28423 /* YOU FM */
+S19.2E-1-1093-28450 /* Bremen Vier */
+S19.2E-1-1093-28402 /* Bayern 3 */
+S19.2E-1-1093-28437 /* NDR 2 */
+S19.2E-1-1093-28440 /* N-JOY */
+S19.2E-1-1093-28420 /* hr2 */
+S19.2E-1-1093-28421 /* hr3 */
+S19.2E-1-1093-28419 /* hr1 */
diff --git a/contrib/postproc.pl b/contrib/postproc.pl
new file mode 100755
index 0000000..eefbd32
--- /dev/null
+++ b/contrib/postproc.pl
@@ -0,0 +1,144 @@
+#!/usr/bin/perl
+use strict;
+
+use File::Basename;
+
+my $LOGGER = "/usr/bin/logger -t " . basename(__FILE__);
+my $mydir = dirname(__FILE__);
+
+# write into logfile for debugging purpose
+my $logfile;
+$logfile= $mydir . '/' . basename(__FILE__, '.pl') . '.log';
+
+# function to write into syslog
+sub Log
+{
+ system("$LOGGER '[audiorecorder]: @_'");
+ print LOGFILE "# @_\n" if (defined($logfile));
+}
+
+if (defined($logfile)) {
+ # open file for debug
+ open(LOGFILE, ">> " . $logfile)
+ or die "Fehler beim Öffnen von '$logfile': $!\n";
+ print LOGFILE "=========================== ". localtime() . "\n";
+ print LOGFILE 'ARGV[0] = ' . $ARGV[0] . "\n";
+ print LOGFILE 'ARGV[1] = ' . $ARGV[1] . "\n";
+ print LOGFILE 'ARGV[2] = ' . $ARGV[2] . "\n";
+ print LOGFILE 'ARGV[3] = ' . $ARGV[3] . "\n";
+ print LOGFILE 'ARGV[4] = ' . $ARGV[4] . "\n";
+ print LOGFILE 'ARGV[5] = ' . $ARGV[5] . "\n";
+ print LOGFILE 'ARGV[6] = ' . $ARGV[6] . "\n";
+ print LOGFILE 'ARGV[7] = ' . $ARGV[7] . "\n";
+ print LOGFILE 'ARGV[8] = ' . $ARGV[8] . "\n";
+ print LOGFILE 'ARGV[9] = ' . $ARGV[9] . "\n";
+}
+
+# test number of parameters
+if ($#ARGV ne 7) {
+ Log("Wrong number of parameters");
+ exit;
+}
+
+# use telling names for parameters
+my $src = $ARGV[0];
+my $src_bitrate = $ARGV[1];
+my $artist = $ARGV[2];
+my $title = $ARGV[3];
+my $channel = $ARGV[4];
+my $event = $ARGV[5];
+my $codec = $ARGV[6];
+my $dst_bitrate = $ARGV[7];
+
+# test parameters
+if ($artist eq "" || $title eq "") {
+ Log("Without data better remove this file");
+ exit;
+}
+
+# Create canonical filename
+my $dst;
+my $tmp = lc($artist . "_-_" . $title);
+# clear nearly all non-characters
+$tmp =~ s/[ \*\?!:"\']/_/g;
+# converts slashes to commas
+$tmp =~ s/\//,/g;
+# convert special characters to ASCII
+$tmp =~ s/[áàå]/a/g;
+$tmp =~ s/ä/ae/g;
+$tmp =~ s/ç/c/g;
+$tmp =~ s/[éÉè]/e/g;
+$tmp =~ s/[íï]/i/g;
+$tmp =~ s/ñ/n/g;
+$tmp =~ s/[óò]/o/g;
+$tmp =~ s/ö/oe/g;
+$tmp =~ s/ù/u/g;
+$tmp =~ s/ü/ue/g;
+# remove multiple underscores
+$tmp =~ s/___*/_/g;
+# remove underscore at file end
+$tmp =~ s/_$//g;
+
+# Name with extension
+$dst = $tmp . "." . $codec;
+
+# open blacklist
+my $inname = $mydir . '/blacklist.m3u';
+if (open(BLACKLIST, "< " . $inname)) {
+ while (<BLACKLIST>) {
+ chomp;
+ if ($_ eq $dst) {
+ Log("File $dst blacklisted");
+ exit;
+ }
+ }
+ close(BLACKLIST)
+}
+
+# calculate sizes
+my $src_size = (-s $src);
+print LOGFILE "size($src) = " . $src_size . "\n" if (defined($logfile));
+
+# No file smaller 8 secs
+if ($src_size < $src_bitrate) {
+ Log("File $src too small ($src_size)!");
+ exit;
+}
+
+# Full qualified name
+my $audiodir = dirname($src) . '/';
+my $fqn = $audiodir . $dst;
+
+# file already exists?
+if (-e $fqn) {
+ # Write duplicate file under curtain circumstances
+ my $size = (-s $fqn);
+ print LOGFILE "size($dst) = " . $size . "\n" if (defined($logfile));
+ # If file size is smaller than 17000 bytes this files are special
+ if ($size < 17000) {
+ Log("File already on CD");
+ exit;
+ }
+
+ my $dst_size = 0;
+ $dst_size = $src_size * $dst_bitrate / $src_bitrate if (!($src_bitrate == 0));
+ print LOGFILE "approx. size($dst) = " . $dst_size . "\n" if (defined($logfile));
+
+ if ($size > $dst_size) {
+ Log("Already larger version on disk ($size vs. $dst_size)");
+ exit;
+ }
+
+ # Files larger than existing ones are stored with
+ # a special numbered extension
+ my $i = 0;
+ while (-e $fqn) {
+ $i++;
+ $dst = $tmp . ".~" . $i . "~." . $codec;
+ $fqn = $audiodir . $dst;
+ }
+ }
+
+print LOGFILE '$dst = ' . $dst . "\n" if (defined($logfile));
+# return file name
+print $dst;
diff --git a/contrib/set_path b/contrib/set_path
new file mode 100755
index 0000000..6b8317b
--- /dev/null
+++ b/contrib/set_path
@@ -0,0 +1,35 @@
+#! /bin/bash
+
+# To active this script, call the audiorecorder-plugin with the
+# parameter -p plus the full path to this script, e.g.
+# "-p /audio/set_path" if this script is stored in the /audio
+# directory, it does not have to be in /usr/bin. Further you have
+# to configure the plugin to use "extern" for the file names.
+
+# The parameters to ths script are
+# $1. source file
+# (e.g. "/audio/S19.2E-1-1093-28450-2007-04-13.16.15.37.tmp.mp2")
+# $2. bitrate of the source file
+# $3. artist
+# $4. title
+# $5. channel
+# $6. event
+# $7. used codec (e.g. "mp3")
+# $8. set bitrate of the target
+
+# The output of the script has to be the file name of the target
+# file. If the file already exists or if there is no output of
+# this script, the audiorecorder plugin does not convert the recording
+# but deletes it.
+
+# In this sample file, the file name of the target file is made
+# of "<artist>_-_<title>.<codec>". Additionally all characters are
+# translated from upper to lower case as well as '/' translated to
+# ',' and '*', '?' resp. ' ' to '_'.
+
+# You can do anything in this script: handle special characters,
+# double underscores, etc. Test wether a song is already stored on CDs,
+# use a blacklist for miserable artists, store multiple versions of
+# recordings, ... Have a look at postproc.pl.
+
+echo -n "$3_-_$4.$7" | tr '[A-Z]/*? ' '[a-z],___'
diff --git a/convert.c b/convert.c
new file mode 100644
index 0000000..237f4c1
--- /dev/null
+++ b/convert.c
@@ -0,0 +1,154 @@
+/*
+ * convert.c
+ */
+
+#include "convert.h"
+
+#include <vdr/tools.h>
+
+
+/* --- cConvert ------------------------------------------------------------- */
+
+cConvert::cConvert(const cPostData &postdata)
+{
+ encoder_buf.data = NULL;
+ encoder_buf.length = 0;
+ encoder_open = -1;
+
+ decoder_buf.data = NULL;
+ decoder_buf.length = 0;
+ decoder_open = -1;
+
+ init_decoder();
+ init_encoder(audio_codecs[postdata.get_codec()], postdata.get_bit_rate(),
+ postdata.get_sample_rate(), postdata.get_channels());
+}
+
+
+cConvert::~cConvert()
+{
+ delete[] encoder_buf.data;
+ delete[] decoder_buf.data;
+
+ if (decoder_open > -1) {
+ avcodec_close(decoder_ctx);
+ av_free(decoder_ctx);
+ }
+
+ if (encoder_open > -1) {
+ avcodec_close(encoder_ctx);
+ av_free(encoder_ctx);
+ }
+}
+
+
+void cConvert::init_decoder(void)
+{
+ decoder_codec = avcodec_find_decoder_by_name("mp2");
+ if (! decoder_codec) {
+ dsyslog("[audiorecorder]: codec mp2 is not supported (%s, "
+ "%s())", __FILE__, __func__);
+ return;
+ }
+
+ decoder_ctx = avcodec_alloc_context();
+ decoder_open = avcodec_open(decoder_ctx, decoder_codec);
+
+ if (decoder_open < 0) {
+ dsyslog("[audiorecorder]: could not open codec mp2 (%s, "
+ "%s())", __FILE__, __func__);
+ return;
+ }
+
+ decoder_buf.data = new uchar[AVCODEC_MAX_AUDIO_FRAME_SIZE];
+
+ dsyslog("[audiorecorder]: decoder initialized (%s, %s())", __FILE__,
+ __func__);
+}
+
+
+void cConvert::init_encoder(const char *codec, int bit_rate, int sample_rate,
+ int channels)
+{
+ encoder_codec = avcodec_find_encoder_by_name(codec);
+ if (! encoder_codec) {
+ dsyslog("[audiorecorder]: codec %s is not supported (%s, "
+ "%s())", codec, __FILE__, __func__);
+ return;
+ }
+
+ encoder_ctx = avcodec_alloc_context();
+
+ encoder_ctx->bit_rate = bit_rate;
+ encoder_ctx->sample_rate = sample_rate;
+ encoder_ctx->channels = channels;
+
+ encoder_open = avcodec_open(encoder_ctx, encoder_codec);
+
+ if (encoder_open < 0) {
+ dsyslog("[audiorecorder]: could not open codec %s (%s, %s())", codec, __FILE__, __func__);
+ return;
+ }
+
+ encoder_buf.length = encoder_ctx->frame_size;
+ encoder_buf.data = new uchar[encoder_buf.length];
+
+ dsyslog("[audiorecorder]: encoder for %s-codec (br: %d, sr: %d, %d ch) "
+ "initialized (%s, %s())", encoder_codec->name,
+ encoder_ctx->bit_rate, encoder_ctx->sample_rate,
+ encoder_ctx->channels, __FILE__, __func__);
+}
+
+
+void cConvert::decode_mpa_frame(mpeg_audio_frame *mpa_frame)
+{
+ if (decoder_open < 0) {
+ decoder_buf.length = 0;
+ return;
+ }
+
+#if LIBAVCODEC_VERSION_INT < ((52<<16)+(0<<8)+0)
+ avcodec_decode_audio(decoder_ctx, (short *)decoder_buf.data,
+ &decoder_buf.length, mpa_frame->data, mpa_frame->length);
+#else
+ decoder_buf.length = AVCODEC_MAX_AUDIO_FRAME_SIZE;
+ avcodec_decode_audio2(decoder_ctx, (short *)decoder_buf.data,
+ &decoder_buf.length, mpa_frame->data, mpa_frame->length);
+#endif
+}
+
+
+abuffer *cConvert::reencode_mpa_frame(mpeg_audio_frame *mpa_frame,
+ float volume)
+{
+ int c, len;
+ short *tmp;
+
+ if (decoder_open < 0 || encoder_open < 0)
+ return &encoder_buf;
+
+ if (strcmp(encoder_codec->name, "mp2") == 0 && volume == 1) {
+ mpa_frame_buf.data = mpa_frame->data;
+ mpa_frame_buf.offset = mpa_frame->length;
+ return &mpa_frame_buf;
+ }
+
+ decode_mpa_frame(mpa_frame);
+
+ if (volume != 1) {
+ /* manipulate the volume */
+ len = decoder_buf.length / sizeof(short);
+ tmp = (short *)decoder_buf.data;
+
+ for (c = 0; c < len; ++c) {
+ *(tmp) = (short)((float)*(tmp) * volume);
+ ++tmp;
+ }
+ }
+
+ encoder_buf.offset = avcodec_encode_audio(encoder_ctx, encoder_buf.data,
+ encoder_buf.length, (short *)decoder_buf.data);
+ /* encoder_buf.offset is used to save the size of the encoded frame */
+
+ return &encoder_buf;
+}
diff --git a/convert.h b/convert.h
new file mode 100644
index 0000000..04fcc4b
--- /dev/null
+++ b/convert.h
@@ -0,0 +1,37 @@
+/*
+ * convert.h
+ */
+
+#ifndef __CONVERT_H
+#define __CONVERT_H
+
+#include "postdata.h"
+#include "mpa-frame.h"
+#include "a-tools.h"
+
+extern "C" {
+#include <avcodec.h>
+}
+
+
+class cConvert {
+private:
+ AVCodec *decoder_codec, *encoder_codec;
+ AVCodecContext *decoder_ctx, *encoder_ctx;
+ int decoder_open, encoder_open;
+
+ abuffer decoder_buf, encoder_buf, mpa_frame_buf;
+
+ void init_decoder(void);
+ void decode_mpa_frame(mpeg_audio_frame *mpa_frame);
+ void init_encoder(const char *codec, int bit_rate, int sample_rate,
+ int channels);
+public:
+ cConvert(const cPostData &postdata);
+ ~cConvert();
+
+ abuffer *reencode_mpa_frame(mpeg_audio_frame *mpa_frame,
+ float volume = 1);
+};
+
+#endif /* __CONVERT_H */
diff --git a/dispatcher.c b/dispatcher.c
new file mode 100644
index 0000000..3b5ff14
--- /dev/null
+++ b/dispatcher.c
@@ -0,0 +1,435 @@
+/*
+ * dispatcher.c
+ */
+
+#include "dispatcher.h"
+#include "setup.h"
+#include "audiorecorder.h"
+#include "postproc.h"
+#include "a-tools.h"
+
+#include <vdr/device.h>
+#include <vdr/channels.h>
+#include <vdr/plugin.h>
+
+#include <unistd.h>
+#include <sys/statfs.h>
+#include <unistd.h>
+
+
+#define COUNTER 30
+
+cRecorderChannels RecorderChannels;
+
+using namespace std;
+
+/* --- cDispatcher----------------------------------------------------------- */
+
+cDispatcher::cDispatcher(void)
+:cThread(), cStatus()
+{
+ tChannelID channel_id;
+ int c;
+
+ active = false;
+
+ /* Load Channels to record */
+
+ RecorderChannels.Load(cPluginAudiorecorder::get_cfg().c_str());
+
+ if (RecorderChannels.Count() < 1) {
+ dsyslog("[audiorecorder]: you must have defined at least one channel for recording in %s (%s, %s()", cPluginAudiorecorder::get_cfg().c_str(),
+ __FILE__, __func__);
+ Skins.QueueMessage(mtInfo, tr("Audiorecorder: No Channels defined !"));
+ return;
+ }
+
+ /* initialize one receiver for each channel */
+ c = 0;
+
+ for (cChannelList *cl = RecorderChannels.First(); cl; cl = RecorderChannels.Next(cl)) {
+ if (cl->Channel()) {
+ dsyslog("[audiorecorder]: Channel to be recorded # %d <%s>, <%s> %s (%s, %s())",
+ c,
+ cl->ChannelString(),
+ cl->Comment(), cl->Channel()->Name(),
+ __FILE__, __func__);
+
+ if (cl->NewAudioReceiver())
+ c++;
+ }
+ }
+
+ dsyslog("[audiorecorder]: Number of Channels : %d - Number of Receivers : %d (%s, %s))", RecorderChannels.Count(), c, __FILE__, __func__);
+
+ if (SetupValues.start_type == 1)
+ Activate(true);
+}
+
+
+cDispatcher::~cDispatcher()
+{
+ Activate(false);
+
+ for (cChannelList *cl = RecorderChannels.First(); cl; cl = RecorderChannels.Next(cl)) {
+ if (cl->AudioReceiver()) {
+ cl->DeleteAudioReceiver();
+ }
+ }
+}
+
+
+void cDispatcher::Activate(bool on)
+{
+ if (on) {
+ if (! active) {
+ active = true;
+ Start();
+ }
+ } else if (active) {
+ active = false;
+ Cancel(3);
+ }
+}
+
+
+void cDispatcher::Action(void)
+{
+ bool interrupted = false;
+ counter = COUNTER;
+
+ dsyslog("[audiorecorder]: dispatcher thread started (%s, %s())",
+ __FILE__, __func__);
+
+ while (active) {
+ sleep(1);
+
+ int num_queued = cPostproc::get_num_queued();
+
+ if (interrupted && num_queued > (SetupValues.max_postproc / 4))
+ continue;
+
+ interrupted = false;
+
+ if (num_queued >= SetupValues.max_postproc) {
+ dsyslog("[audiorecorder]: max. postprocessings in queue"
+ " achieved (%d), detaching all receivers (%s, "
+ "%s())", num_queued, __FILE__, __func__);
+
+ interrupted = true;
+ detach_receivers(-1, 0);
+
+ continue;
+ }
+
+ if (! check_free_disc_space()) {
+ dsyslog("[audiorecorder]: min. free disc space on %s "
+ "reached (%d mb), recording will be stopped "
+ "(%s, %s())",
+ cPluginAudiorecorder::get_recdir().c_str(),
+ SetupValues.min_free_space, __FILE__,
+ __func__);
+
+ active = false;
+ continue;
+ }
+
+ int receivers = get_attached_receivers(-1);
+ if (SetupValues.max_receivers > receivers) {
+ if (counter > 0)
+ counter--;
+ else
+ attach_receivers();
+ }
+ else if (SetupValues.max_receivers < receivers)
+ detach_receivers(-1, SetupValues.max_receivers);
+ }
+
+ detach_receivers(-1, 0);
+
+ dsyslog("[audiorecorder]: dispatcher thread stopped (%s, %s())",
+ __FILE__, __func__);
+}
+
+
+void cDispatcher::ChannelSwitch(const cDevice *device, int channel_number)
+{
+ /*
+ * workaround to detach active audioreceivers if the attached device
+ * is switched to another transponder. this should be done inside of
+ * vdr, but there are situations which lead to emergency exits of vdr
+ * without this peace of code (for example if a recording starts).
+ *
+ */
+
+ int device_number;
+
+ if (channel_number == 0) {
+ counter = COUNTER;
+ return;
+ }
+
+ device_number = device->DeviceNumber();
+
+ if (get_attached_receivers(device_number) == 0)
+ return;
+
+ if (Channels.GetByNumber(channel_number)->Transponder() ==
+ get_transponder_of_first_receiver(device_number))
+ return;
+
+ detach_receivers(device_number, 0);
+}
+
+
+int cDispatcher::get_attached_receivers(int device_number)
+{
+ int count = 0;
+
+ for (cChannelList *cl = RecorderChannels.First(); cl; cl = RecorderChannels.Next(cl)) {
+ if (cl->AudioReceiver()) {
+ if (cl->AudioReceiver()->is_attached(device_number)) {
+ ++count;
+ }
+ }
+ }
+ return count;
+}
+
+
+int cDispatcher::get_recording_receivers(void)
+{
+ int count = 0;
+
+ for (cChannelList *cl = RecorderChannels.First(); cl; cl = RecorderChannels.Next(cl)) {
+ if (cl->AudioReceiver()) {
+ if (cl->AudioReceiver()->is_recording()) {
+ ++count;
+ }
+ }
+ }
+ return count;
+}
+
+
+int cDispatcher::get_recording_status(const cChannel *channel)
+{
+ for (cChannelList *cl = RecorderChannels.First(); cl; cl = RecorderChannels.Next(cl)) {
+ if (cl->AudioReceiver() && cl->AudioReceiver()->get_channel() == channel) {
+ if (cl->AudioReceiver()->is_recording())
+ return 3;
+ else if (cl->AudioReceiver()->is_attached(-1))
+ return 2;
+ else
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int cDispatcher::get_no_of_channels(void) {
+ return RecorderChannels.Count();
+}
+
+void cDispatcher::detach_receivers(int detach_device_number,
+ int devices_remaining)
+{
+ int device_number;
+ cDevice *device = NULL;
+
+ Lock();
+
+ for (cChannelList *cl = RecorderChannels.First(); cl; cl = RecorderChannels.Next(cl)) {
+ if (get_attached_receivers(-1) <= devices_remaining)
+ break;
+
+ if (!cl->AudioReceiver())
+ continue;
+
+ device_number = cl->AudioReceiver()->get_device_number();
+
+ if (device_number < 0)
+ continue;
+
+ if (detach_device_number != device_number && detach_device_number > 0)
+ continue;
+
+ device = cDevice::GetDevice(device_number);
+
+ if (! device)
+ continue;
+
+ device->Detach(cl->AudioReceiver());
+ }
+ Unlock();
+}
+
+
+void cDispatcher::attach_receivers(void)
+{
+ cAudioReceiver *receiver;
+ cDevice *device;
+ bool needs_detach = false;
+
+ Lock();
+
+ for (cChannelList *cl = RecorderChannels.First(); cl; cl = RecorderChannels.Next(cl)) {
+ if (get_attached_receivers(-1) >= SetupValues.max_receivers)
+ break;
+
+ receiver = cl->AudioReceiver();
+
+ if (! receiver || receiver->is_attached(-1))
+ continue;
+
+ device = cDevice::GetDevice(receiver->get_channel(), 0, needs_detach);
+
+ if (! device || (device && needs_detach))
+ continue;
+
+#ifndef AUDIORCORDER_DEVEL
+ if (device == cDevice::ActualDevice() &&
+ ! device->IsTunedToTransponder(receiver->get_channel()))
+ continue;
+#endif
+ if (! device->IsTunedToTransponder(receiver->get_channel()) &&
+ ! device->SwitchChannel(receiver->get_channel(), false))
+ continue;
+
+ receiver->set_device_number(device->DeviceNumber());
+ device->AttachReceiver(receiver);
+ }
+
+ Unlock();
+}
+
+
+int cDispatcher::get_transponder_of_first_receiver(int device_number)
+{
+ int transponder = 0;
+
+ for (cChannelList *cl = RecorderChannels.First(); cl; cl = RecorderChannels.Next(cl)) {
+ if (! cl->AudioReceiver())
+ continue;
+
+ if (cl->AudioReceiver()->is_attached(device_number)) {
+ transponder = cl->AudioReceiver()->get_channel()->Transponder();
+ break;
+ }
+ }
+
+ return transponder;
+}
+
+
+bool cDispatcher::check_free_disc_space(void)
+{
+ struct statfs stat_fs;
+
+ if (statfs(cPluginAudiorecorder::get_recdir().c_str(), &stat_fs) != 0)
+ return false;
+
+ if ((uint64_t)stat_fs.f_bavail * (uint64_t)stat_fs.f_bsize <
+ (uint64_t)SetupValues.min_free_space * (uint64_t)1024 *
+ (uint64_t)1024)
+ return false;
+
+ return true;
+}
+
+// -- cRecorderChannels --------------------------------------------------------------
+
+bool cRecorderChannels::Load(const char *filename, bool dummy)
+{
+ dsyslog("[audiorecorder]: <%s> (%s, %s()", filename, __FILE__, __func__);
+
+ if(cConfig < cChannelList >::Load(filename, true)) {
+ SetSource(First());
+ return true;
+ }
+ return false;
+}
+
+// -- cChannelList --------------------------------------------------------------
+
+cChannelList::cChannelList(void)
+{
+ audioreceiver = NULL;
+}
+
+cChannelList::cChannelList(const char *_channelstring, const char *_comment)
+{
+ Set(_channelstring, _comment);
+}
+
+cChannelList::~cChannelList()
+{
+ if (audioreceiver) {
+ delete audioreceiver;
+ }
+ if (channelstring) {
+ free(channelstring);
+ }
+ if (comment) {
+ free(comment);
+ }
+}
+
+void cChannelList::Set(const char *_channelstring, const char *_comment)
+{
+ channelstring = strdup(_channelstring);
+ comment = strdup(_comment);
+ channel_id = tChannelID::FromString(channelstring);
+
+ if (! channel_id.Valid()) {
+ dsyslog("[audiorecorder]: channel %s is not valid (%s, %s())", channelstring, __FILE__, __func__);
+ } else {
+ channel = Channels.GetByChannelID(channel_id, true, true);
+ if (channel) {
+ dsyslog("[audiorecorder]: channel %s set (%s, %s())", channel->Name(), __FILE__, __func__);
+ } else {
+ dsyslog("[audiorecorder]: channel not set : %s %d, %d, %d, %d, %d (%s, %s())", channelstring,
+ channel_id.Source(),
+ channel_id.Nid(),
+ channel_id.Tid(),
+ channel_id.Sid(),
+ channel_id.Rid(),
+ __FILE__, __func__);
+ }
+ }
+}
+
+cAudioReceiver *cChannelList::NewAudioReceiver()
+{
+ if (channel) {
+ audioreceiver = new cAudioReceiver(channel);
+ return audioreceiver;
+ }
+ return NULL;
+}
+
+void cChannelList::DeleteAudioReceiver(void) {
+ if (audioreceiver)
+ delete audioreceiver;
+}
+
+bool cChannelList::Parse(char *s)
+{
+ char cs[256], comment[256], *p1;
+
+ int fields = sscanf(s, "%s", cs);
+ if (fields >= 1) {
+ p1 = s;
+ p1 += strlen(cs) + 1;
+
+ strncpy(comment, p1, 255);
+ comment[255] = 0;
+
+ Set(cs, comment);
+ return true;
+ } else {
+ return false;
+ }
+}
+
diff --git a/dispatcher.h b/dispatcher.h
new file mode 100644
index 0000000..0dde64d
--- /dev/null
+++ b/dispatcher.h
@@ -0,0 +1,93 @@
+/*
+ * dispatcher.h
+ */
+
+#ifndef __DISPATCHER_H
+#define __DISPATCHER_H
+
+#include "audioreceiver.h"
+
+#include <vdr/device.h>
+#include <vdr/thread.h>
+#include <vdr/status.h>
+
+class cChannelList : public cListObject {
+ char *channelstring;
+ char *comment;
+
+ tChannelID channel_id;
+ cChannel *channel;
+
+ cAudioReceiver *audioreceiver;
+public:
+ cChannelList(void);
+ cChannelList(const char *_channelstring, const char *_comment);
+ void Set(const char *_channelstring, const char *_comment);
+ cAudioReceiver *NewAudioReceiver();
+ void DeleteAudioReceiver(void);
+ bool Parse(char *s);
+ const char *ChannelString(void) { return channelstring; }
+ const char *Comment(void) { return comment; }
+ cAudioReceiver *AudioReceiver(void) { return audioreceiver; }
+ cChannel *Channel(void) { return channel; }
+ ~cChannelList();
+};
+
+
+class cRecorderChannels : public cConfig<cChannelList> {
+ cChannelList *current;
+public:
+ virtual bool Load(const char *filename, bool dummy=false);
+ void SetSource(cChannelList *source) { current=source; }
+ cChannelList *GetChannel(void) { return current; }
+};
+
+class cDispatcher : public cThread, cStatus {
+private:
+ bool active;
+ int counter;
+ // cAudioReceiver **audioreceivers;
+
+ void attach_receivers(void);
+
+ int get_transponder_of_first_receiver(int device_number);
+ /*
+ * returns the transponder of the first attached audioreceiver on the
+ * given device
+ */
+
+ void detach_receivers(int detach_device_number, int devices_remaining);
+ /*
+ * detaches all audioreceivers on the given device_number, until
+ * devices_remaining are remaining.
+ * if detach_device_number == -1, all audioreceivers are
+ * detached.
+ */
+
+ bool check_free_disc_space(void);
+protected:
+ virtual void Activate(bool on);
+ virtual void Action(void);
+
+ virtual void ChannelSwitch(const cDevice *device, int channel_number);
+public:
+ cDispatcher(void);
+ ~cDispatcher();
+
+ void stop(void) { Activate(false); }
+ void start(void) { Activate(true); }
+ bool is_active(void) { return active; }
+
+ int get_recording_receivers(void);
+ int get_recording_status(const cChannel *channel);
+ int get_attached_receivers(int device_number);
+ int get_no_of_channels(void);
+
+ /*
+ * returns the number of the attached audioreceivers on the given
+ * device. if device_number == -1, the number of all attached
+ * audioreceivers is returned.
+ */
+};
+
+#endif /* __DISPATCHER_H */
diff --git a/external-player.h b/external-player.h
new file mode 100644
index 0000000..84dce1c
--- /dev/null
+++ b/external-player.h
@@ -0,0 +1,15 @@
+/*
+ * external-player.h
+ */
+
+#ifndef __EXTERNAL_PLAYER_H
+#define __EXTERNAL_PLAYER_H
+
+struct MP3ServiceData {
+ int result;
+ union {
+ const char *filename;
+ } data;
+ };
+
+#endif //__EXTERNAL_PLAYER_H
diff --git a/mainmenu.c b/mainmenu.c
new file mode 100644
index 0000000..469686d
--- /dev/null
+++ b/mainmenu.c
@@ -0,0 +1,90 @@
+/*
+ * mainmenu.c
+ */
+
+#include "mainmenu.h"
+#include "browse.h"
+#include "dispatcher.h"
+#include "audiorecorder.h"
+
+#include <sstream>
+
+
+using namespace std;
+
+/* --- cMainmenu ------------------------------------------------------------ */
+
+cMainmenu::cMainmenu(cDispatcher *_dispatcher)
+:cOsdMenu("Audiorecorder, Mainmenu")
+{
+ dispatcher = _dispatcher;
+ active = dispatcher->is_active();
+
+ Clear();
+
+ Add(new cOsdItem(tr("Browse tracks"), osUser1));
+ Add(new cOsdItem(active ? tr("Stop receiving") : tr("Start receiving"),
+ osUser2));
+ Add(new cOsdItem(tr("Start rebuilding cache"), osUser3));
+
+ Display();
+}
+
+
+eOSState cMainmenu::ProcessKey(eKeys key)
+{
+ eOSState state = cOsdMenu::ProcessKey(key);
+
+ if (HasSubMenu())
+ return osContinue;
+
+ switch (state) {
+ case osBack:
+ state = osEnd;
+ break;
+ case osUser1:
+ if (Cache.is_rebuilding()) {
+ Skins.Message(mtStatus, tr("Rebuilding cache, try later"));
+ break;
+ }
+
+ AddSubMenu(new cBrowse());
+
+ break;
+ case osUser2:
+ if (active) {
+ Add(new cOsdItem(tr("Start receiving"), osUser2), false,
+ Get(Current()));
+ dispatcher->stop();
+ active = false;
+ }
+ else {
+ // Message if there are no channels to record
+ if (dispatcher->get_no_of_channels() < 1) {
+ char* tmp = NULL;
+ asprintf(&tmp, tr("no channel in %s"),
+ cPluginAudiorecorder::get_cfg().c_str());
+ Skins.Message(mtStatus, tmp);
+ free(tmp);
+ return state;
+ }
+ Add(new cOsdItem(tr("Stop receiving"), osUser2), false,
+ Get(Current()));
+ dispatcher->start();
+ active = true;
+ }
+ Del(Current());
+ Display();
+
+ break;
+ case osUser3:
+ Cache.rebuild();
+ Skins.Message(mtStatus, tr("Rebuilding cache"));
+
+ break;
+ default:
+ break;
+ }
+
+ return state;
+}
diff --git a/mainmenu.h b/mainmenu.h
new file mode 100644
index 0000000..4482296
--- /dev/null
+++ b/mainmenu.h
@@ -0,0 +1,29 @@
+/*
+ * mainmenu.h
+ */
+
+#ifndef __MAINMENU_H
+#define __MAINMENU_H
+
+#include "dispatcher.h"
+#include "cache.h"
+
+#include <vdr/osdbase.h>
+#include <vdr/plugin.h>
+
+#include <string>
+
+#include "audiorecorder_i18n.h"
+
+
+class cMainmenu : public cOsdMenu {
+private:
+ bool active;
+ cDispatcher *dispatcher;
+public:
+ cMainmenu(cDispatcher *_dispatcher);
+ virtual eOSState ProcessKey(eKeys key);
+};
+
+
+#endif /* __MAINMENU_H */
diff --git a/mpa-frame.c b/mpa-frame.c
new file mode 100644
index 0000000..1d7f03f
--- /dev/null
+++ b/mpa-frame.c
@@ -0,0 +1,55 @@
+/*
+ * mpa-frame.c
+ */
+
+
+#include "mpa-frame.h"
+#include "audiorecorder.h"
+
+#include <vdr/tools.h>
+
+#include <iostream>
+
+
+using namespace std;
+
+const int sample_rates[] = { 44100, 48000, 32000, 0 };
+const int bit_rates[] = { 0, 32000, 48000, 56000, 64000, 80000, 96000, 112000,
+ 128000, 160000, 192000, 224000, 256000, 320000, 384000, 0 };
+
+void get_mpa_frame(abuffer *buf, mpeg_audio_frame *mpa_frame,
+ const char *description)
+{
+ int c;
+
+ mpa_frame->data = NULL;
+
+ for (c = buf->offset; c < buf->length - buf->offset - 2; ++c) {
+ if ((buf->data[c] == 0xff) && (buf->data[c + 1] & 0xe0) &&
+ (buf->data[c + 1] & 0x1e) == 0x1c) {
+ /* mpeg v1, layer II header found */
+ mpa_frame->bit_rate = bit_rates[buf->data[c + 2] >> 4];
+ mpa_frame->sample_rate = sample_rates[(buf->data[c + 2]
+ >> 2) & 0x03];
+ if (mpa_frame->sample_rate < 1)
+ continue;
+ mpa_frame->channels = (buf->data[c + 3] >> 6) == 3 ?
+ 1 : 2;
+ int p = buf->data[c + 2] & 0x01; /* padding bit */
+ mpa_frame->length = 144 * mpa_frame->bit_rate /
+ mpa_frame->sample_rate + p;
+
+ if (c + mpa_frame->length <= buf->length)
+ mpa_frame->data = buf->data + c;
+
+ break;
+ }
+ }
+
+ if (c > buf->offset && cPluginAudiorecorder::get_dbg_level() > 0)
+ cout << "skipped " << c - buf->offset << " byte(s) "
+ << description << "> (" << __FILE__ << ", " << __func__
+ << "())" << endl;
+
+ buf->offset = c;
+}
diff --git a/mpa-frame.h b/mpa-frame.h
new file mode 100644
index 0000000..7f4c4ef
--- /dev/null
+++ b/mpa-frame.h
@@ -0,0 +1,24 @@
+/*
+ * mpa-frame.h
+ */
+
+#ifndef __MPA_FRAME__H
+#define __MPA_FRAME__H
+
+#include "a-tools.h"
+
+
+typedef struct mpeg_audio_frame {
+ uchar *data;
+ int length;
+ int bit_rate;
+ int sample_rate;
+ int channels;
+} mpeg_audio_frame;
+
+extern const int bit_rates[];
+
+void get_mpa_frame(abuffer *buf, mpeg_audio_frame *mpa_frame,
+ const char *description);
+
+#endif /* __MPA_FRAME_H */
diff --git a/po/de_DE.po b/po/de_DE.po
new file mode 100644
index 0000000..bc231d0
--- /dev/null
+++ b/po/de_DE.po
@@ -0,0 +1,240 @@
+# VDR plugin language source file.
+# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de>
+# This file is distributed under the same license as the VDR plugin package.
+# Klaus Schmidinger <kls@cadsoft.de>, 2000
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: vdr-audiorecorder-2.0.0\n"
+"Report-Msgid-Bugs-To: <cwieninger@gmx.de>\n"
+"POT-Creation-Date: 2009-03-09 19:14+0100\n"
+"PO-Revision-Date: 2007-08-19 08:20+0200\n"
+"Last-Translator: Joerg Bornkessel <hd_brummy@gentoo.org>\n"
+"Language-Team: <vdr@linuxtv.org>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "floods your disc with music"
+msgstr ""
+
+msgid "mp2"
+msgstr "mp2"
+
+msgid "mp3"
+msgstr "mp3"
+
+msgid "off"
+msgstr "aus"
+
+msgid "linear"
+msgstr "linear"
+
+msgid "exponential"
+msgstr "exponentiell"
+
+msgid "all"
+msgstr "alle"
+
+msgid "by artist"
+msgstr "nach Künstler"
+
+msgid "by channel"
+msgstr "nach Kanal"
+
+msgid "by date"
+msgstr "nach Datum"
+
+msgid "Artist/Title"
+msgstr "Künstler/Titel"
+
+msgid "Artist - Title"
+msgstr "Künstler - Titel"
+
+msgid "Channel/Artist/Title"
+msgstr "Kanal/Künstler/Titel"
+
+msgid "Channel - Artist - Title"
+msgstr "Kanal - Künstler - Titel"
+
+msgid "Audiorecorder"
+msgstr "Audiorecorder"
+
+msgid "on"
+msgstr "an"
+
+msgid "collapse all"
+msgstr "Alles zuklappen"
+
+msgid "expand all"
+msgstr "Alles aufklappen"
+
+msgid "action"
+msgstr "Aktion"
+
+msgid "Browse tracks"
+msgstr "Titel durchsuchen"
+
+msgid "unknown"
+msgstr "unbekannt"
+
+msgid "No external player-plugin found"
+msgstr "Kein externes Player-Plugin gefunden"
+
+msgid "Playing all tracks in "
+msgstr "Alle Titel spielen in "
+
+msgid "Playing "
+msgstr "Spielt "
+
+msgid "Audiorecorder, Action"
+msgstr "Audiorecorder, Aktion"
+
+msgid "Category : "
+msgstr "Kategorie : "
+
+msgid "Play all tracks"
+msgstr "Alle Titel spielen"
+
+msgid "More to come ..."
+msgstr "Es kommt noch mehr ..."
+
+msgid "Track: "
+msgstr "Titel: "
+
+msgid "Play track"
+msgstr "Titel spielen"
+
+msgid "back"
+msgstr "zurück"
+
+msgid "Audiorecorder, Info"
+msgstr "Audiorecorder, Info"
+
+msgid "File"
+msgstr "Datei"
+
+msgid "Artist"
+msgstr "Künstler"
+
+msgid "Title"
+msgstr "Titel"
+
+msgid "Album"
+msgstr "Album"
+
+msgid "Genre"
+msgstr "Genre"
+
+msgid "Track"
+msgstr "Titel"
+
+msgid "Year"
+msgstr "Jahr"
+
+msgid "Channel"
+msgstr "Kanal"
+
+msgid "Event"
+msgstr "Ereignis"
+
+msgid "Date"
+msgstr "Datum"
+
+msgid "Time"
+msgstr "Uhrzeit"
+
+msgid "Audiorecorder: rebuilding cache finished"
+msgstr "Audiorecorder: Cache Neuaufbau fertig"
+
+msgid "Audiorecorder: loading of cache finished"
+msgstr "Audiorecorder: Cache-Laden fertig"
+
+msgid "Audiorecorder: No Channels defined !"
+msgstr "Audiorecorder: Keine Kanäle definiert !"
+
+msgid "Start receiving"
+msgstr "Aufnahmemodus starten"
+
+msgid "Stop receiving"
+msgstr "Aufnahmemodus beenden"
+
+msgid "Start rebuilding cache"
+msgstr "Cache neu aufbauen"
+
+msgid "Rebuilding cache, try later"
+msgstr "Cache wird neu aufgebaut - später nocheinmal versuchen"
+
+msgid "no channel in %s"
+msgstr "Kein Kanal in %s"
+
+msgid "Rebuilding cache"
+msgstr "Cache wird neu aufgebaut"
+
+#, fuzzy
+msgid "Channel/Artist - Title"
+msgstr "Kanal/Künstler - Titel"
+
+msgid "External"
+msgstr "Extern"
+
+msgid "--- Receiver: ---"
+msgstr "--- Aufnahme: ---"
+
+msgid "Receiving mode on start"
+msgstr "Aufnahmemodus beim Start"
+
+msgid "Max. number of receivers"
+msgstr "Max. Anzahl paralleler Aufnahmen"
+
+msgid "Min. free disc space (in mb)"
+msgstr "Min. freier Plattenplatz (in MB)"
+
+msgid "--- Browser: ---"
+msgstr "--- Browser: ---"
+
+msgid "Default view"
+msgstr "Default Anzeige"
+
+msgid "--- Postprocessing: ---"
+msgstr "--- Nachbearbeitung: ---"
+
+msgid "Pause if osd is opened"
+msgstr "Pausieren wenn OSD offen ist"
+
+msgid "Max. tracks in queue"
+msgstr "Max. Titel in Queue"
+
+msgid "Fade in mode"
+msgstr "Fade-In Modus"
+
+msgid "Fade in seconds"
+msgstr "Fade-In Sekunden"
+
+msgid "Fade out mode"
+msgstr "Fade-Out Modus"
+
+msgid "Fade out seconds"
+msgstr "Fade-Out Sekunden"
+
+msgid "Audio codec"
+msgstr "Audiokodierung"
+
+msgid "Bitrate (if audio codec isn't <original>)"
+msgstr "Bitrate (wenn Audiokodierung nicht original ist)"
+
+msgid "File Pattern"
+msgstr "Dateimuster"
+
+msgid "Upper Case"
+msgstr "Großschreibung"
+
+msgid "No"
+msgstr "Nein"
+
+msgid "Yes"
+msgstr "Ja"
+
+msgid "Copies"
+msgstr "Kopien"
diff --git a/postdata.c b/postdata.c
new file mode 100644
index 0000000..0c7eef6
--- /dev/null
+++ b/postdata.c
@@ -0,0 +1,135 @@
+/*
+ * postdata.c
+ */
+
+#include "postdata.h"
+#include "setup.h"
+#include "mpa-frame.h"
+#include "audiorecorder.h"
+
+#include <vdr/epg.h>
+#include <vdr/plugin.h>
+
+#include <iostream>
+#include <sstream>
+#include <time.h>
+
+
+using namespace std;
+
+/* --- cPostData ------------------------------------------------------------ */
+
+cPostData::cPostData(const cChannel *_channel)
+:cTrackInfo()
+{
+ channel = _channel;
+ set_channel(channel->Name());
+}
+
+
+cPostData::~cPostData()
+{
+}
+
+
+void cPostData::start_track(void)
+{
+ set_recpath_date_time();
+ set_event();
+
+ file_pattern = SetupValues.file_pattern;
+ upper = SetupValues.upper;
+ copies = SetupValues.copies;
+ dsyslog("[audiorecorder]: (file_pattern : %d) (%s, %s())", file_pattern, __FILE__, __func__);
+}
+
+
+void cPostData::stop_track(int _bit_rate, int _sample_rate, int _channels,
+ int _len_mpa_frame)
+{
+ sample_rate = _sample_rate;
+ channels = _channels;
+ len_mpa_frame = _len_mpa_frame;
+
+ set_codec(SetupValues.audio_codec);
+
+ if (SetupValues.audio_codec == 0) {
+ bit_rate = _bit_rate;
+ }
+ else {
+ bit_rate = bit_rates[SetupValues.bit_rate + 1];
+ }
+
+ /* TODO: adjust this for other codecs then mp2/mp3 */
+ frame_len = 144 * bit_rate / sample_rate;
+
+ set_bit_rates(_bit_rate, bit_rate);
+ set_path(file_pattern, upper);
+ set_fade_values();
+}
+
+
+void cPostData::set_recpath_date_time(void)
+{
+ time_t now;
+ struct tm *tm_now, tm_store;
+ char path_str[20], date_str[11], time_str[6];
+ stringstream tmp;
+
+ time(&now);
+ tm_now = localtime_r(&now, &tm_store);
+
+ strftime(path_str, 20, "%Y-%m-%d.%H.%M.%S", tm_now);
+ strftime(date_str, 11, "%Y-%m-%d", tm_now);
+ strftime(time_str, 6, "%H.%M", tm_now);
+
+/*
+ tmp << cPluginAudiorecorder::get_recdir()
+ << *channel->GetChannelID().ToString() << "-" << path_str
+ << ".tmp.mp2";
+*/
+ tmp << cPluginAudiorecorder::get_recdir()
+ << channel->Name() << "-" << path_str
+ << ".tmp.mp2";
+
+ set_recpath(tmp.str());
+ set_date(date_str);
+ set_time(time_str);
+}
+
+
+void cPostData::set_event(void)
+{
+ cSchedulesLock sched_lock;
+ const cSchedules *schedules = cSchedules::Schedules(sched_lock);
+ if (! schedules)
+ return;
+
+ const cSchedule *sched = schedules->GetSchedule(channel);
+ if (! sched)
+ return;
+
+ const cEvent *event = sched->GetPresentEvent();
+ if (! event)
+ return;
+
+ cTrackInfo::set_event(event->Title());
+}
+
+
+void cPostData::set_fade_values()
+{
+ fade_in_mode = SetupValues.fade_in_mode;
+ if (fade_in_mode == 0)
+ frames_fade_in = 0;
+ else
+ frames_fade_in = SetupValues.fade_in * (bit_rate / 8 /
+ frame_len);
+
+ fade_out_mode = SetupValues.fade_out_mode;
+ if (fade_out_mode == 0)
+ frames_fade_out = 0;
+ else
+ frames_fade_out = SetupValues.fade_out * (bit_rate / 8 /
+ frame_len);
+}
diff --git a/postdata.h b/postdata.h
new file mode 100644
index 0000000..6631e6f
--- /dev/null
+++ b/postdata.h
@@ -0,0 +1,62 @@
+/*
+ * postdata.h
+ */
+
+#ifndef __POSTDATA_H
+#define __POSTDATA_H
+
+#include "trackinfo.h"
+
+#include <vdr/channels.h>
+
+#include <string>
+
+
+class cPostData : public cTrackInfo {
+private:
+ const cChannel *channel;
+ int bit_rate;
+ int sample_rate;
+ int channels;
+ int frame_len;
+
+ int len_mpa_frame;
+
+ int fade_in_mode;
+ int frames_fade_in;
+ int fade_out_mode;
+ int frames_fade_out;
+ int file_pattern;
+ int upper;
+ int copies;
+
+ void set_recpath_date_time(void);
+ void set_event(void);
+
+ void set_fade_values(void);
+public:
+ cPostData(const cChannel *_channel);
+ ~cPostData();
+
+ void start_track(void);
+ void stop_track(int _bit_rate, int _sample_rate, int _channels,
+ int _len_mpa_frame);
+
+ int get_bit_rate(void) const { return bit_rate; }
+ int get_sample_rate(void) const { return sample_rate; }
+ int get_channels(void) const { return channels; }
+
+ int get_frame_len(void) const { return frame_len; }
+
+ int get_len_mpa_frame(void) const { return len_mpa_frame; }
+
+ int get_fade_in_mode(void) const { return fade_in_mode; }
+ int get_frames_fade_in(void) const { return frames_fade_in; }
+ int get_fade_out_mode(void) const { return fade_out_mode; }
+ int get_frames_fade_out(void) const { return frames_fade_out; }
+ int get_file_pattern(void) const { return file_pattern; }
+ int get_upper(void) const { return upper; }
+ int get_copies(void) const { return copies; }
+};
+
+#endif /* __POSTDATA_H */
diff --git a/postproc.c b/postproc.c
new file mode 100644
index 0000000..45f69d1
--- /dev/null
+++ b/postproc.c
@@ -0,0 +1,380 @@
+/*
+ * postproc.c
+ */
+
+
+#include "postproc.h"
+#include "postdata.h"
+#include "cache.h"
+#include "setup.h"
+#include "audiorecorder.h"
+
+#include <vdr/osd.h>
+#include <vdr/tools.h>
+
+#include <taglib/mpegfile.h>
+#include <taglib/tag.h>
+
+#include <fstream>
+#include <sstream>
+#include <cstdio>
+/* this include is needed for gcc 2.95: */
+#include <vector>
+
+
+using namespace std;
+
+/* --- cPostproc -------------------------------------------------------------*/
+
+list<cPostData> cPostproc::postlist;
+cMutex cPostproc::mutex;
+
+cPostproc::cPostproc()
+:cThread()
+{
+ active = false;
+
+ convert = NULL;
+
+ file_buf.length = KB(10);
+ file_buf.data = new uchar[file_buf.length];
+
+ Activate(true);
+}
+
+
+cPostproc::~cPostproc()
+{
+ Activate(false);
+
+ mutex.Lock();
+ while (! postlist.empty()) {
+ postdata = postlist.begin();
+ remove(postdata->get_recpath().c_str());
+
+ postlist.pop_front();
+ }
+ mutex.Unlock();
+
+ DELETE(convert);
+ delete[] file_buf.data;
+}
+
+
+void cPostproc::Activate(bool on)
+{
+ if (on) {
+ if (! active) {
+ active = true;
+ Start();
+ }
+ }
+ else if (active) {
+ active = false;
+ Cancel(3);
+ }
+}
+
+
+void cPostproc::Action(void)
+{
+ dsyslog("[audiorecorder]: postprocessing thread started (%s, %s())",
+ __FILE__, __func__);
+
+ while (active) {
+ if (postlist.empty()) {
+ usleep(10000);
+ continue;
+ }
+
+ bool tag = true;
+
+ postdata = postlist.begin();
+
+ if (postdata->get_frames_fade_in() > 0 ||
+ postdata->get_frames_fade_out() > 0 ||
+ strcmp(audio_codecs[postdata->get_codec()], "mp2") != 0) {
+ convert = new cConvert(*postdata);
+ tag = reencode();
+ }
+ else
+ rename_file();
+
+ if (tag) {
+ set_tag();
+ Cache.add_track(*postdata);
+ }
+
+ mutex.Lock();
+ postlist.pop_front();
+ mutex.Unlock();
+
+ DELETE(convert);
+ }
+
+ dsyslog("[audiorecorder]: postprocessing thread stopped (%s, %s())",
+ __FILE__, __func__);
+}
+
+
+void cPostproc::add_track(const cPostData *track)
+{
+ if (track->get_path().empty() || track->get_recpath().empty())
+ return;
+
+ if (access(track->get_path().c_str(), F_OK) != -1) {
+ remove(track->get_recpath().c_str());
+ dsyslog("[audiorecorder]: track (%s) already exists (%s, "
+ "%s())", track->get_path().c_str(), __FILE__,
+ __func__);
+ return;
+ }
+
+ mutex.Lock();
+ postlist.push_back(*track);
+ mutex.Unlock();
+}
+
+
+int cPostproc::get_num_queued(void)
+{
+ int num;
+
+ mutex.Lock();
+ num = postlist.size();
+ mutex.Unlock();
+
+ return num;
+}
+
+
+bool cPostproc::reencode(void)
+{
+ ifstream infile;
+ infile.open(postdata->get_recpath().c_str());
+ if (! infile) {
+ dsyslog("[audiorecorder]: can't open input file (%s) (%s, "
+ "%s())", postdata->get_recpath().c_str(), __FILE__,
+ __func__);
+ return false;
+ }
+
+ dsyslog("[audiorecorder]: (recpath : %s) (%s, %s())", postdata->get_recpath().c_str(), __FILE__, __func__);
+ dsyslog("[audiorecorder]: (path : %s) (%s, %s())", postdata->get_path().c_str(), __FILE__, __func__);
+ dsyslog("[audiorecorder]: (recdir : %s) (%s, %s())", postdata->get_recdir().c_str(), __FILE__, __func__);
+ dsyslog("[audiorecorder]: (artist : %s) (%s, %s())", postdata->get_artist().c_str(), __FILE__, __func__);
+ dsyslog("[audiorecorder]: (title : %s) (%s, %s())", postdata->get_title().c_str(), __FILE__, __func__);
+ dsyslog("[audiorecorder]: (file_pattern : %d) (%s, %s())", postdata->get_file_pattern(), __FILE__, __func__);
+ dsyslog("[audiorecorder]: (upper : %d) (%s, %s())", postdata->get_upper(), __FILE__, __func__);
+//dsyslog("[audiorecorder]: (channel : %s) (%s, %s())", postdata->get_channel(), __FILE__, __func__);
+
+ stringstream tmp;
+ std::string path;
+ ofstream outfile;
+
+ int pos, res = 0;
+ std::string dir, pfad, dirpfad;
+
+ dirpfad = postdata->get_recdir();
+ pfad = postdata->get_path();
+ pos = pfad.find(dirpfad);
+
+ if (pos >= 0) {
+ dir = pfad;
+ dir.erase(0, dirpfad.length());
+ dsyslog("[audiorecorder]: dir : %s (%s, %s())", dir.c_str(), __FILE__,
+ __func__);
+
+ pos = dir.find("/");
+
+ while (pos >= 0 && res == 0) {
+ if (pos == 0) {
+ dir.erase(0, 1);
+ } else {
+ dirpfad.append("/");
+ dirpfad.append(dir.substr(0, pos));
+ dir.erase(0, pos + 1);
+
+ res = test_and_create(dirpfad.c_str());
+
+ dsyslog("[audiorecorder]: 5 dirpfad : %d %s (%s, %s())", res, dirpfad.c_str(), __FILE__,
+ __func__);
+ }
+ dsyslog("[audiorecorder]: 6 dir : %s (%s, %s())", dir.c_str(), __FILE__,
+ __func__);
+ pos = dir.find("/");
+ }
+ }
+
+ outfile.open(postdata->get_path().c_str());
+
+ if (! outfile) {
+ dsyslog("[audiorecorder]: can't open output file (%s) (%s, "
+ "%s())", postdata->get_path().c_str(), __FILE__,
+ __func__);
+ return false;
+ }
+
+ infile.seekg(0, ios::end);
+ int file_pos = infile.tellg();
+ infile.seekg(0, ios::beg);
+
+ int frames = file_pos / postdata->get_len_mpa_frame();
+
+ dsyslog("[audiorecorder]: start reencoding (%s into %s) (%s, %s())",
+ postdata->get_recpath().c_str(), postdata->get_path().c_str(),
+ __FILE__, __func__);
+
+ int frame = 0;
+ file_pos = 0;
+ file_buf.offset = 0;
+
+ while (active && ! infile.eof()) {
+ infile.seekg(file_pos + file_buf.offset, ios::beg);
+ file_pos = infile.tellg();
+ file_buf.offset = 0;
+
+ infile.read((char *)file_buf.data, file_buf.length);
+
+ get_mpa_frame(&file_buf, &mpa_frame,
+ postdata->get_recpath().c_str());
+
+ while(active && mpa_frame.data) {
+ file_buf.offset += mpa_frame.length;
+
+ /* pause postprocessing if osd is opened */
+ while (active && SetupValues.pause == 1 &&
+ cOsd::IsOpen())
+ usleep(10000);
+
+ float volume = get_volume(frame, frames);
+ abuffer *encoder_buf =
+ convert->reencode_mpa_frame(&mpa_frame, volume);
+
+ if (encoder_buf->data) {
+ outfile.write((char *)encoder_buf->data,
+ encoder_buf->offset);
+ /*
+ * encoder_buf->offset is used to save the
+ * size of the encoded frame ...
+ */
+ ++frame;
+ }
+
+ get_mpa_frame(&file_buf, &mpa_frame,
+ postdata->get_recpath().c_str());
+ }
+ }
+
+ infile.close();
+ outfile.close();
+
+ remove(postdata->get_recpath().c_str());
+
+ if (! active) {
+ remove(postdata->get_path().c_str());
+ return false;
+ }
+
+ dsyslog("[audiorecorder]: stop reencoding (%s into %s) (%s, %s())",
+ postdata->get_recpath().c_str(), postdata->get_path().c_str(),
+ __FILE__, __func__);
+
+ return true;
+}
+
+
+float cPostproc::get_volume(int _frame, int _frames)
+{
+ int fade;
+
+ if (_frame < postdata->get_frames_fade_in()) {
+ fade = postdata->get_frames_fade_in();
+
+ if (postdata->get_fade_in_mode() == 1)
+ return (float)_frame / (float)fade;
+ else if (postdata->get_fade_in_mode() == 2)
+ return (float)(_frame * _frame) / (float)(fade * fade);
+ }
+ else if (_frame > _frames - postdata->get_frames_fade_out()) {
+ fade = postdata->get_frames_fade_out();
+ _frame = _frames - _frame;
+
+ if (postdata->get_fade_out_mode() == 1)
+ return (float)_frame / (float)fade;
+ else if (postdata->get_fade_out_mode() == 2)
+ return (float)(_frame * _frame) / (float)(fade * fade);
+ }
+
+ return 1;
+}
+
+
+void cPostproc::set_tag()
+{
+ TagLib::MPEG::File file(postdata->get_path().c_str());
+ TagLib::Tag *tag = file.tag();
+
+ if (! tag)
+ return;
+
+ tag->setArtist(postdata->get_artist());
+ tag->setTitle(postdata->get_title());
+
+ if (! postdata->get_album().empty())
+ tag->setAlbum(postdata->get_album());
+
+ if (! postdata->get_genre().empty())
+ tag->setGenre(postdata->get_genre());
+
+ if (! postdata->get_channel().empty()) {
+ cPlugin *plugin = cPluginManager::GetPlugin("audiorecorder");
+
+ stringstream tmp;
+
+ tmp << "recorded on \"" << (postdata->get_event().empty() ?
+ "" : postdata->get_event()) << "@"
+ << postdata->get_channel() << "\" (vdr-audiorecorder "
+ << plugin->Version() << ")";
+
+ tag->setComment(tmp.str().c_str());
+ }
+
+ file.save();
+
+ dsyslog("[audiorecorder]: tag written (%s) (%s, %s())",
+ postdata->get_path().c_str(), __FILE__, __func__);
+}
+
+
+void cPostproc::rename_file()
+{
+ rename(postdata->get_recpath().c_str(), postdata->get_path().c_str());
+
+ dsyslog("[audiorecorder]: renamed %s to %s (%s, %s())",
+ postdata->get_recpath().c_str(), postdata->get_path().c_str(),
+ __FILE__, __func__);
+}
+
+int cPostproc::test_and_create(const char *dirname)
+{
+ struct stat statbuf;
+
+ int res;
+
+ res = stat(dirname, &statbuf);
+
+ if (res != 0) {
+ if (mkdir(dirname, S_IRWXU | S_IRWXG | S_IXOTH | S_IROTH))
+ return 0;
+ else
+ return 1;
+ } else {
+ if (statbuf.st_mode & S_IFDIR) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+}
+
diff --git a/postproc.h b/postproc.h
new file mode 100644
index 0000000..44d9242
--- /dev/null
+++ b/postproc.h
@@ -0,0 +1,48 @@
+/*
+ * postproc.h
+ */
+
+#ifndef __POSTPROC_H
+#define __POSTPROC_H
+
+#include "postdata.h"
+#include "convert.h"
+#include "mpa-frame.h"
+#include "a-tools.h"
+
+#include <vdr/thread.h>
+
+#include <list>
+
+
+class cPostproc : public cThread {
+private:
+ bool active;
+
+ static std::list<cPostData> postlist;
+ static cMutex mutex;
+ std::list<cPostData>::iterator postdata;
+
+ abuffer file_buf;
+ mpeg_audio_frame mpa_frame;
+ cConvert *convert;
+
+ bool reencode(void);
+ void fade_in(void);
+ void fade_out(void);
+ float get_volume(int _frame, int _frames);
+ void set_tag(void);
+ void rename_file(void);
+ int test_and_create(const char *dirname);
+protected:
+ virtual void Action(void);
+ virtual void Activate(bool on);
+public:
+ cPostproc(void);
+ ~cPostproc();
+
+ static void add_track(const cPostData *track);
+ static int get_num_queued(void);
+};
+
+#endif /* __POSTPROC_H */
diff --git a/rds.c b/rds.c
new file mode 100644
index 0000000..b204b0d
--- /dev/null
+++ b/rds.c
@@ -0,0 +1,386 @@
+/*
+ * rds.c
+ */
+
+#include "rds.h"
+#include "audiorecorder.h"
+
+#include <vdr/tools.h>
+
+#include <iostream>
+#include <string.h>
+
+
+using namespace std;
+
+/*
+ * rt-translation: 0x80..0xff
+ * this table is taken from the vdr-radio-plugin, thx a lot ...
+ */
+const uchar rt_trans[128] = {
+ 0xe1, 0xe0, 0xe9, 0xe8, 0xed, 0xec, 0xf3, 0xf2,
+ 0xfa, 0xf9, 0xd1, 0xc7, 0x8c, 0xdf, 0x8e, 0x8f,
+ 0xe2, 0xe4, 0xea, 0xeb, 0xee, 0xef, 0xf4, 0xf6,
+ 0xfb, 0xfc, 0xf1, 0xe7, 0x9c, 0x9d, 0x9e, 0x9f,
+ 0xaa, 0xa1, 0xa9, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xa3, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ 0xba, 0xb9, 0xb2, 0xb3, 0xb1, 0xa1, 0xb6, 0xb7,
+ 0xb5, 0xbf, 0xf7, 0xb0, 0xbc, 0xbd, 0xbe, 0xa7,
+ 0xc1, 0xc0, 0xc9, 0xc8, 0xcd, 0xcc, 0xd3, 0xd2,
+ 0xda, 0xd9, 0xca, 0xcb, 0xcc, 0xcd, 0xd0, 0xcf,
+ 0xc2, 0xc4, 0xca, 0xcb, 0xce, 0xcf, 0xd4, 0xd6,
+ 0xdb, 0xdc, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+ 0xc3, 0xc5, 0xc6, 0xe3, 0xe4, 0xdd, 0xd5, 0xd8,
+ 0xde, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xf0,
+ 0xe3, 0xe5, 0xe6, 0xf3, 0xf4, 0xfd, 0xf5, 0xf8,
+ 0xfe, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff };
+
+
+/* map rds pty-codes 10-15 and 24-28 to id3v1 tags */
+const char *ptys[29] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, "Pop", "Rock", "Easy Listening", "Classical",
+ "Classical", "Other", NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, "Jazz", "Country", "Folklore", "Oldies", "Folk" };
+
+/* --- cRds ----------------------------------------------------------------- */
+
+
+cRds::cRds(cPostData *_postdata)
+{
+ postdata = _postdata;
+ recstat = recWait;
+
+ buf.data = new uchar[RDS_BUF_SIZE];
+ buf.length = 0;
+ buf.offset = 0;
+
+ rt_length = 0;
+ lb0xfd = false;
+
+ last_tb = -1;
+ last_rb = -1;
+}
+
+
+cRds::~cRds()
+{
+ delete[] buf.data;
+}
+
+
+void cRds::put_data(uchar *data, int length)
+{
+ int c, pos, len;
+
+ pos = length - 1;
+ len = data[pos - 1]; /* length of rds data */
+
+ if (data[pos] != 0xfd || len == 0)
+ return;
+
+ if (buf.length + len >= RDS_BUF_SIZE) {
+ dsyslog("[audiorecorder]: buffer overflow <%s> (%s, %s())",
+ postdata->get_channel().c_str(), __FILE__, __func__);
+ buf.length = 0;
+ buf.offset = 0;
+ }
+
+ /* reverse rds data */
+ for (c = 2; c < len + 2; ++c) {
+
+ /* byte stuffing */
+ int bs = data[pos - c];
+
+ if (bs == 0xfd) {
+ lb0xfd = true;
+ continue;
+ }
+
+ if (lb0xfd) {
+ switch (bs) {
+ case 0x00:
+ bs = 0xfd;
+ break;
+ case 0x01:
+ bs = 0xfe;
+ break;
+ default:
+ bs = 0xff;
+ }
+
+ lb0xfd = false;
+ }
+
+ /* copy rds value on the buffer */
+ buf.data[buf.length] = bs;
+ ++buf.length;
+ }
+}
+
+
+bool cRds::set_next_frame(void)
+{
+ int offset;
+
+ offset = buf.offset;
+ rds_frame.data = NULL;
+ rds_frame.length = 0;
+
+ for (; buf.offset < buf.length - 4; ++buf.offset) {
+ if (buf.data[buf.offset] == 0xfe) {
+ /* rds start marker found */
+ rds_frame.length = buf.data[buf.offset + 4] + 8;
+
+ if (buf.offset + rds_frame.length > buf.length)
+ break;
+
+ rds_frame.data = buf.data + buf.offset;
+
+ /* check rds end marker */
+ if (rds_frame.data[rds_frame.length - 1] != 0xff)
+ dsyslog("[audiorecorder]: no rds end marker "
+ "found <%s> (%s, %s())",
+ postdata->get_channel().c_str(),
+ __FILE__, __func__);
+
+ break;
+ }
+ }
+
+ if (buf.offset != offset && cPluginAudiorecorder::get_dbg_level() > 0)
+ cout << "skipped " << (buf.offset - offset) << " byte(s) <"
+ << postdata->get_channel() << "> (" << __FILE__
+ << ", " << __func__ << "())" << endl;
+
+ if (! rds_frame.data) {
+ delete_data(buf.offset);
+ return false;
+ }
+
+ buf.offset += rds_frame.length;
+
+ return true;
+}
+
+
+void cRds::delete_data(int length)
+{
+ /* clear the buffer */
+
+ if (length < 1)
+ return;
+
+ buf.length -= length;
+ buf.offset -= length;
+ memmove(buf.data, buf.data + length, buf.length);
+}
+
+
+eRecStat cRds::decode_frame(void)
+{
+ /* reset recording status */
+ switch (recstat) {
+ case recStart:
+ recstat = recRun;
+ break;
+ case recStop:
+ recstat = recOff;
+ break;
+ default:
+ break;
+ }
+
+
+ if (rds_frame.data[5] == mecRT)
+ decode_radiotext();
+ else if (rds_frame.data[5] == mecODA && rds_frame.data[7] == 0x4b &&
+ rds_frame.data[8] == 0xd7)
+ decode_rtp();
+ else if (rds_frame.data[5] == mecPTY) {
+ int pty = rds_frame.data[8];
+
+ if (recstat == recRun && postdata->get_genre().empty()) {
+ if ((pty > 9 && pty < 16) || (pty > 23 && pty < 29))
+ postdata->set_genre(ptys[pty]);
+ }
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "pty-code <" << postdata->get_channel() <<
+ ">: " << pty << endl;
+ }
+
+ return recstat;
+}
+
+
+void cRds::decode_radiotext(void)
+{
+ int c, rt_ab_flag;
+
+ rt_length = rds_frame.data[8] - 1;
+ rt_ab_flag = rds_frame.data[9] & 0x01;
+
+ for (c = 0; c < rt_length; ++c) {
+ if (rds_frame.data[c + 10] >= 0x80)
+ rds_frame.data[c + 10] =
+ rt_trans[(rds_frame.data[c + 10] - 0x80)];
+
+ radiotext[c] = rds_frame.data[c + 10];
+ }
+
+ radiotext[rt_length] = '\0';
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "radiotext (" << rt_ab_flag << ") <" <<
+ postdata->get_channel() << ">: " << radiotext << endl;
+}
+
+
+void cRds::decode_rtp(void)
+{
+ int rb, tb;
+
+ bool toggle_tb = false;
+ bool toggle_rb = false;
+
+ tb = (rds_frame.data[10] >> 4) & 0x01;
+ if (last_tb == -1)
+ last_tb = tb;
+
+ rb = (rds_frame.data[10] >> 3) & 0x01;
+ if (last_rb == -1)
+ last_rb = rb;
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "rtp-data <" << postdata->get_channel() << ">: toggle "
+ "bit: " << tb << ", running bit: " << rb << endl;
+
+ if (last_tb != tb)
+ toggle_tb = true;
+
+ if (last_rb != rb)
+ toggle_rb = true;
+
+ last_tb = tb;
+ last_rb = rb;
+
+ if (recstat == recWait) {
+ if (! toggle_tb && ! toggle_rb)
+ return;
+
+ /* ready to record */
+ recstat = recOff;
+ }
+
+ if (rb == 1) {
+ /* running bit is on */
+ if (recstat == recOff) {
+ recstat = recStart;
+ rt_length = 0;
+ }
+ else if (toggle_tb) {
+ recstat = recStop;
+ return;
+ }
+ }
+ else {
+ /* running bit is off */
+ if (recstat == recRun)
+ recstat = recStop;
+
+ return;
+ }
+
+ if (recstat == recRun && rt_length > 0)
+ decode_rtp_items();
+}
+
+
+void cRds::decode_rtp_items(void)
+{
+ int c, t[2], s[2], l[2];
+
+ /* tag 1 */
+ t[0] = ((rds_frame.data[10] << 3) & 0x38) | (rds_frame.data[11] >> 5);
+ s[0] = ((rds_frame.data[11] << 1) & 0x3e) | (rds_frame.data[12] >> 7);
+ l[0] = ((rds_frame.data[12] >> 1) & 0x3f) + 1;
+
+ /* tag 2*/
+ t[1] = ((rds_frame.data[12] << 5) & 0x20) | (rds_frame.data[13] >> 3);
+ s[1] = ((rds_frame.data[13] << 3) & 0x38) | (rds_frame.data[14] >> 5);
+ l[1] = (rds_frame.data[14] & 0x1f) + 1;
+
+ for (c = 0; c < 2; ++c) {
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "rtp-data <" << postdata->get_channel() << ">: "
+ "type: " << t[c] << ", start:" << s[c] <<
+ ", length: " << l[c] << endl;
+
+ if (t[c] < 1 || t[c] > 10)
+ continue;
+
+ if (t[c] == ItemTitle) {
+ if (correct_rtp_tag(t[c], s[c], l[c]))
+ postdata->set_title(radiotext + s[c]);
+ }
+ else if (t[c] == ItemArtist) {
+ if (correct_rtp_tag(t[c], s[c], l[c]))
+ postdata->set_artist(radiotext + s[c]);
+ }
+ else if (t[c] == ItemAlbum) {
+ if (correct_rtp_tag(t[c], s[c], l[c]))
+ postdata->set_album(radiotext + s[c]);
+ }
+ else if (t[c] == ItemTrack)
+ postdata->set_track(atoi(radiotext + s[c]));
+ else if (t[c] == ItemGenre) {
+ if (correct_rtp_tag(t[c], s[c], l[c]))
+ postdata->set_genre(radiotext + s[c]);
+ }
+ }
+}
+
+
+bool cRds::correct_rtp_tag(int &type, int &start, int &length)
+{
+ int original_length = length;
+
+ if (start + length > rt_length) {
+ length = rt_length - start;
+
+ if (original_length - length > 1 || length < 0) {
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "rtp-data <" << postdata->get_channel()
+ << ">: got buggy tag-infos, could not "
+ "correct the length value" << endl;
+ return false;
+ }
+ }
+
+ /* remove ' ', '"' or "-" at the beginning of the tag */
+ int end = start + length;
+ for (; start < end; ++start) {
+ if (radiotext[start] != ' ' && radiotext[start] != '"' &&
+ radiotext[start] != '-')
+ break;
+
+ --length;
+ }
+
+ /* remove ' ' or '"' at the end of the tag */
+ for (; length > 0; length--) {
+ if (radiotext[start + length - 1] != ' ' && radiotext[start +
+ length - 1] != '"')
+ break;
+ }
+
+ if (length <= 1 && cPluginAudiorecorder::get_dbg_level() > 1) {
+ cout << "rtp-data <" << postdata->get_channel() << ">: got "
+ "buggy tag-infos, length is too short !" << endl;
+ return false;
+ }
+
+ radiotext[start + length] = '\0';
+
+ return true;
+}
diff --git a/rds.h b/rds.h
new file mode 100644
index 0000000..0dad138
--- /dev/null
+++ b/rds.h
@@ -0,0 +1,60 @@
+/*
+ * rds.h
+ */
+
+#ifndef __RDS_H
+#define __RDS_H
+
+#include "recstat.h"
+#include "postdata.h"
+#include "a-tools.h"
+
+
+#define RDS_BUF_SIZE 263*2
+
+enum eMec {
+ mecNone,
+ mecPTY = 0x07,
+ mecRT = 0x0a,
+ mecODA = 0x46
+};
+
+enum {
+ ItemTitle = 1,
+ ItemAlbum,
+ ItemTrack,
+ ItemArtist,
+ ItemComposition, /* not supported */
+ ItemMovement, /* not supported */
+ ItemComposer, /* not supported */
+ ItemBand, /* not supported */
+ ItemComment,
+ ItemGenre /* not supported */
+};
+
+class cRds {
+private:
+ abuffer buf, rds_frame;
+ char radiotext[65];
+ int rt_length;
+ bool lb0xfd;
+ int last_tb, last_rb;
+ eRecStat recstat;
+
+ cPostData *postdata;
+
+ void decode_radiotext(void);
+ void decode_rtp(void);
+ void decode_rtp_items(void);
+ bool correct_rtp_tag(int &type, int &start, int &length);
+ void delete_data(int length);
+public:
+ cRds(cPostData *_postdata);
+ ~cRds();
+
+ void put_data(uchar *data, int length);
+ bool set_next_frame(void);
+ eRecStat decode_frame(void);
+};
+
+#endif /* __RDS_H */
diff --git a/recstat.h b/recstat.h
new file mode 100644
index 0000000..556070f
--- /dev/null
+++ b/recstat.h
@@ -0,0 +1,17 @@
+/*
+ * recstat.h
+ */
+
+#ifndef __RECSTAT_H
+#define __RECSTAT_H
+
+
+enum eRecStat {
+ recWait, /* wait for the first toggle */
+ recOff, /* no active recording */
+ recStart, /* start recording */
+ recRun, /* active recording */
+ recStop, /* stop recording */
+};
+
+#endif /* __RECSTAT_H */
diff --git a/service.h b/service.h
new file mode 100644
index 0000000..2fd4b14
--- /dev/null
+++ b/service.h
@@ -0,0 +1,27 @@
+/*
+ * service.h
+ */
+
+#ifndef __SERVICE_H
+#define __SERVICE_H
+
+#include <vdr/channels.h>
+
+
+/*
+ * service-id: "Audiorecorder-StatusRtpChannel-v1.0"
+ * give me the channel, and i will set the actual status.
+ */
+
+struct Audiorecorder_StatusRtpChannel_v1_0 {
+ const cChannel *channel;
+ int status;
+ /*
+ * 0 = channel is unknown
+ * 1 = channel is known, but no receiver is attached to this channel
+ * 2 = receiver is attached, but there is no actual recording
+ * 3 = actual recording
+ */
+};
+
+#endif /* __SERVICE_H */
diff --git a/setup.c b/setup.c
new file mode 100644
index 0000000..db5ee78
--- /dev/null
+++ b/setup.c
@@ -0,0 +1,104 @@
+/*
+ * setup.c
+ */
+
+#include "setup.h"
+#include "mpa-frame.h"
+
+
+/* default setup values: */
+struct setup_values SetupValues = {
+ 1, /* receiving mode on start is 'on' */
+ 5, /* max. receivers */
+ 50, /* min. free space (in mb) */
+ 3, /* default view 'by date' */
+ 1, /* pause postprocessing if osd is opened */
+ 8, /* max. tracks in postprocessing queue */
+ 1, /* fade in mode */
+ 9, /* fade in seconds */
+ 2, /* fade out mode */
+ 12, /* fade out seconds */
+ 0, /* audio codec */
+ NUM_CODECS, /* number of audio codecs */
+ 9, /* bitrate */
+ 0 /* file_pattern */
+};
+
+
+const char *audio_codecs[NUM_CODECS] = { "mp2", "libmp3lame", "mp3" };
+
+const char *audio_codecs_translated[NUM_CODECS] = { tr("mp2"), tr("mp3"), tr("mp3") };
+
+const char *fade_types[NUM_FADE_TYPES] = { tr("off"), tr("linear"), tr("exponential") };
+
+const char *_bit_rates[] = { "0", "32000", "48000", "56000", "64000", "80000", "96000", "112000", "128000", "160000", "192000", "224000", "256000", "320000", "384000", "0" };
+
+const char *views[NUM_VIEWS] = { tr("all"), tr("by artist"), tr("by channel"), tr("by date") };
+
+const char *file_patterns[NUM_FILE_PATTERNS] = { tr("Artist/Title"), tr("Artist - Title"), tr("Channel/Artist/Title"), tr("Channel - Artist - Title"), tr("Channel/Artist - Title"), tr("External") };
+
+/* --- cAudiorecorderSetup -------------------------------------------------- */
+
+cAudiorecorderSetup::cAudiorecorderSetup(void)
+:cMenuSetupPage()
+{
+ setupvalues = SetupValues;
+
+ Add(new cOsdItem(tr("--- Receiver: ---"), osUnknown, false));
+ Add(new cMenuEditBoolItem(tr("Receiving mode on start"),
+ &setupvalues.start_type, tr("off"), tr("on")));
+ Add(new cMenuEditIntItem(tr("Max. number of receivers"),
+ &setupvalues.max_receivers, 0, 12));
+ Add(new cMenuEditIntItem(tr("Min. free disc space (in mb)"),
+ &setupvalues.min_free_space, 50, 999999));
+
+ Add(new cOsdItem(tr("--- Browser: ---"), osUnknown, false));
+ Add(new cMenuEditStraItem(tr("Default view"), &setupvalues.default_view,
+ NUM_VIEWS, views));
+
+ Add(new cOsdItem(tr("--- Postprocessing: ---"), osUnknown, false));
+ Add(new cMenuEditBoolItem(tr("Pause if osd is opened"),
+ &setupvalues.pause));
+ Add(new cMenuEditIntItem(tr("Max. tracks in queue"),
+ &setupvalues.max_postproc, 1, 20));
+ Add(new cMenuEditStraItem(tr("Fade in mode"), &setupvalues.fade_in_mode,
+ NUM_FADE_TYPES, fade_types));
+ Add(new cMenuEditIntItem(tr("Fade in seconds"), &setupvalues.fade_in,
+ 0, 20));
+ Add(new cMenuEditStraItem(tr("Fade out mode"), &setupvalues.fade_out_mode,
+ NUM_FADE_TYPES, fade_types));
+ Add(new cMenuEditIntItem(tr("Fade out seconds"), &setupvalues.fade_out,
+ 0, 20));
+ Add(new cMenuEditStraItem(tr("Audio codec"), &setupvalues.audio_codec,
+ setupvalues.num_audio_codecs, audio_codecs_translated));
+ Add(new cMenuEditStraItem(tr("Bitrate (if audio codec isn't <original>)"),
+ &setupvalues.bit_rate, 14, &_bit_rates[1]));
+ Add(new cMenuEditStraItem(tr("File Pattern"),
+ &setupvalues.file_pattern, NUM_FILE_PATTERNS, file_patterns));
+ Add(new cMenuEditBoolItem(tr("Upper Case"),
+ &setupvalues.upper, tr("No"), tr("Yes")));
+ Add(new cMenuEditIntItem(tr("Copies"), &setupvalues.copies,
+ 0, 20));
+}
+
+
+void cAudiorecorderSetup::Store(void)
+{
+ SetupValues = setupvalues;
+
+ SetupStore("default_view", SetupValues.default_view);
+ SetupStore("start_type", SetupValues.start_type);
+ SetupStore("max_receivers", SetupValues.max_receivers);
+ SetupStore("min_free_space", SetupValues.min_free_space);
+ SetupStore("pause", SetupValues.pause);
+ SetupStore("max_postproc", SetupValues.max_postproc);
+ SetupStore("fade_in_mode", SetupValues.fade_in_mode);
+ SetupStore("fade_in", SetupValues.fade_in);
+ SetupStore("fade_out_mode", SetupValues.fade_out_mode);
+ SetupStore("fade_out", SetupValues.fade_out);
+ SetupStore("audio_codec", audio_codecs_translated[SetupValues.audio_codec]);
+ SetupStore("bit_rate", SetupValues.bit_rate);
+ SetupStore("file_pattern", SetupValues.file_pattern);
+ SetupStore("upper", SetupValues.upper);
+ SetupStore("copies", SetupValues.copies);
+}
diff --git a/setup.h b/setup.h
new file mode 100644
index 0000000..7194632
--- /dev/null
+++ b/setup.h
@@ -0,0 +1,56 @@
+/*
+ * setup.h
+ */
+
+#ifndef __SETUP_H
+#define __SETUP_H
+
+#include <vdr/menuitems.h>
+
+
+struct setup_values {
+ int start_type;
+ int max_receivers;
+ int min_free_space;
+
+ int default_view;
+
+ int pause;
+ int max_postproc;
+ int fade_in_mode;
+ int fade_in;
+ int fade_out_mode;
+ int fade_out;
+ int audio_codec;
+ int num_audio_codecs;
+ int bit_rate;
+ int file_pattern;
+ int upper;
+ int copies;
+};
+
+extern struct setup_values SetupValues;
+
+#define NUM_CODECS 3
+extern const char *audio_codecs[NUM_CODECS];
+extern const char *audio_codecs_translated[NUM_CODECS];
+
+#define NUM_FADE_TYPES 3
+extern const char *fade_types[NUM_FADE_TYPES];
+
+#define NUM_VIEWS 4
+extern const char *views[NUM_VIEWS];
+
+#define NUM_FILE_PATTERNS 6
+extern const char *file_patterns[NUM_FILE_PATTERNS];
+
+class cAudiorecorderSetup : public cMenuSetupPage {
+private:
+ struct setup_values setupvalues;
+protected:
+ virtual void Store(void);
+public:
+ cAudiorecorderSetup(void);
+};
+
+#endif /* __SETUP_H */
diff --git a/trackinfo.c b/trackinfo.c
new file mode 100644
index 0000000..91b895e
--- /dev/null
+++ b/trackinfo.c
@@ -0,0 +1,360 @@
+/*
+ * trackinfo.c
+ */
+
+#include "trackinfo.h"
+#include "audiorecorder.h"
+
+#include <vdr/tools.h>
+
+#include <iostream>
+
+
+using namespace std;
+
+/* --- cTrackInfo ----------------------------------------------- */
+
+
+cTrackInfo::cTrackInfo(void)
+{
+ track = 0;
+ year = 0;
+}
+
+
+void cTrackInfo::clear(void)
+{
+ recpath.erase();
+ path.erase();
+ artist.erase();
+ title.erase();
+ album.erase();
+ track = 0;
+ year = 0;
+ genre.erase();
+ comment.erase();
+ event.erase();
+ recdate.erase();
+ rectime.erase();
+ codec = 0;
+ recdir.erase();
+}
+
+
+void cTrackInfo::set_recpath(const string &_recpath)
+{
+ if (_recpath.empty() || ! recpath.empty())
+ return;
+
+ recpath = _recpath;
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- recpath set to: " << recpath << " ---" << endl;
+}
+
+
+void cTrackInfo::set_path(int _file_pattern, int _upper)
+{
+ if (artist.empty() || title.empty())
+ return;
+
+ stringstream tmp;
+
+ patch_chars(artist);
+ patch_chars(title);
+
+ if (_upper) {
+ string_toupper(artist);
+ string_toupper(title);
+ string_toupper(album);
+ string_toupper(channel);
+ string_toupper(event);
+ }
+
+ switch(_file_pattern) {
+ case 0 : // Artist/Title
+ tmp << artist << "/" << title << "." << audio_codecs_translated[codec];
+ break;
+ case 1 : // Artist - Title
+ tmp << artist << "-" << title << "." << audio_codecs_translated[codec];
+ break;
+ case 2 : // Station/Artist/Title
+ tmp << channel << "/" << artist << "/" << title << "." << audio_codecs_translated[codec];
+ break;
+ case 3 : // Station - Artist - Title
+ tmp << channel << "-" << artist << "-" << title << "." << audio_codecs_translated[codec];
+ break;
+ case 4 : // Station/Artist - Title
+ tmp << channel << "/" << artist << "-" << title << "." << audio_codecs_translated[codec];
+ break;
+ case 5 : // External
+ tmp << path_external();
+ break;
+ }
+
+ path = tmp.str();
+ partial_path = tmp.str();
+
+ recdir.insert(0, cPluginAudiorecorder::get_recdir());
+
+ if (!path.empty())
+ path.insert(0, cPluginAudiorecorder::get_recdir());
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- path set to: " << path << " ---" << endl;
+}
+
+void cTrackInfo::set_path(const string &_path)
+{
+ if (_path.empty()) {
+ path = "";
+ partial_path = "";
+ } else {
+ path = _path;
+ partial_path = path;
+ recdir = cPluginAudiorecorder::get_recdir();
+ partial_path.erase(0, recdir.length());
+ }
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- path set to: " << path << " ---" << endl;
+}
+
+
+void cTrackInfo::set_date(const char *_date)
+{
+ if (! _date || ! recdate.empty())
+ return;
+
+ recdate = _date;
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- date set to: " << recdate << " ---" << endl;
+}
+
+
+void cTrackInfo::set_time(const char *_time)
+{
+ if (! _time || ! rectime.empty())
+ return;
+
+ rectime = _time;
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- time set to: " << rectime << " ---" << endl;
+}
+
+void cTrackInfo::set_artist(const char *_artist)
+{
+ if (! _artist || ! artist.empty())
+ return;
+
+ artist = _artist;
+ // string_toupper(artist);
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- artist set to: " << artist << " ---" << endl;
+}
+
+
+void cTrackInfo::set_title(const char *_title)
+{
+ if (! _title || ! title.empty())
+ return;
+
+ title = _title;
+ // string_toupper(title);
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- title set to: " << title << " ---" << endl;
+}
+
+
+void cTrackInfo::set_album(const char *_album)
+{
+ if (! _album || ! album.empty())
+ return;
+
+ album = _album;
+ // string_toupper(album);
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- album set to: " << album << " ---" << endl;
+}
+
+
+void cTrackInfo::set_track(int _track)
+{
+ if (_track == 0 || track != 0)
+ return;
+
+ track = _track;
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- track set to: " << track << " ---" << endl;
+}
+
+
+void cTrackInfo::set_year(int _year)
+{
+ if (_year == 0 || year != 0)
+ return;
+
+ year = _year;
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- year set to: " << year << " ---" << endl;
+}
+
+
+void cTrackInfo::set_genre(const char *_genre)
+{
+ if (! _genre || ! genre.empty())
+ return;
+
+ genre = _genre;
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- genre set to: " << genre << " ---" << endl;
+}
+
+
+void cTrackInfo::set_comment(const char *_comment)
+{
+ if (! _comment || ! comment.empty())
+ return;
+
+ comment = _comment;
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- comment set to: " << comment << " ---" << endl;
+}
+
+
+void cTrackInfo::set_channel(const string &_channel)
+{
+ if (_channel.empty() || ! channel.empty())
+ return;
+
+ channel = _channel;
+ // string_toupper(channel);
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- channel set to: " << channel << " ---" << endl;
+}
+
+
+void cTrackInfo::set_event(const string &_event)
+{
+ if (_event.empty() || ! event.empty())
+ return;
+
+ event = _event;
+ // string_toupper(event);
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- event set to: " << event << " ---" << endl;
+}
+
+
+/*
+void cTrackInfo::set_codec(const char *_codec)
+{
+ if (! _codec || ! codec.empty())
+ return;
+
+ codec = _codec;
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- codec set to: " << codec << " ---" << endl;
+}
+*/
+
+void cTrackInfo::set_codec(int _codec)
+{
+ if (_codec < 0 || _codec >= NUM_CODECS)
+ return;
+
+ codec = _codec;
+
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "--- codec set to: " << codec << " ---" << endl;
+}
+
+
+void cTrackInfo::set_bit_rates(int _mpa_frame_bit_rate, int _bit_rate)
+{
+ mpa_frame_bit_rate = _mpa_frame_bit_rate;
+ bit_rate = _bit_rate;
+}
+
+void cTrackInfo::string_toupper(string &str)
+{
+ for (string::iterator i = str.begin(); i != str.end(); ++i)
+ *i = toupper((unsigned char)*i);
+}
+
+void cTrackInfo::patch_chars(string &_string)
+{
+ for (string::iterator i = _string.begin(); i != _string.end(); ++i) {
+ if ((char)*i == '*' || (char)*i == '?')
+ *i = '_';
+ else if ((char)*i == '/')
+ *i = ',';
+ }
+}
+
+std::string cTrackInfo::path_external(void)
+{
+
+ char *cmdbuf = NULL;
+ char *result = NULL;
+
+ dsyslog("[audiorecorder]: LT 1 (%s, %s())",
+ __FILE__, __func__);
+
+ asprintf(&cmdbuf, "\"%s\" \"%s\" \"%d\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%d\"",
+ cPluginAudiorecorder::get_pscript().c_str(),
+ recpath.c_str(),
+ mpa_frame_bit_rate,
+ artist.c_str(),
+ title.c_str(),
+ channel.c_str(),
+ event.c_str(),
+ audio_codecs_translated[codec],
+ bit_rate
+ );
+
+ dsyslog("[audiorecorder]: LT 2 (%s, %s())",
+ __FILE__, __func__);
+
+ dsyslog("[audiorecorder]: executing command '%s' (%s, %s())",
+ cmdbuf,
+ __FILE__, __func__);
+
+ cReadLine pipe;
+
+ dsyslog("[audiorecorder]: LT 3 (%s, %s())",
+ __FILE__, __func__);
+
+ FILE *p = popen(cmdbuf, "r");
+ dsyslog("[audiorecorder]: LT 4 (%s, %s())",
+ __FILE__, __func__);
+
+ if (p != (FILE *)NULL) {
+ result = pipe.Read(p);
+ dsyslog("[audiorecorder]: received '%s' (%s, %s())",
+ result,
+ __FILE__, __func__);
+ pclose(p);
+ } else {
+ dsyslog("[audiorecorder]: ERROR: can't open pipe for command '%s' (%s, %s())",
+ cmdbuf,
+ __FILE__, __func__);
+ }
+ free(cmdbuf);
+
+ dsyslog("[audiorecorder]: LT 5 (%s, %s())",
+ __FILE__, __func__);
+ return (result == NULL) ? "" : result;
+}
diff --git a/trackinfo.h b/trackinfo.h
new file mode 100644
index 0000000..9361598
--- /dev/null
+++ b/trackinfo.h
@@ -0,0 +1,69 @@
+/*
+ * trackinfo.h
+ */
+
+#ifndef __TRACKINFO_H
+#define __TRACKINFO_H
+
+#include <string>
+#include "setup.h"
+
+
+class cTrackInfo {
+private:
+ std::string recpath, path, artist, title, album, genre, comment,
+ channel, event, recdate, rectime, recdir, partial_path;
+
+ int track, year;
+ int mpa_frame_bit_rate, bit_rate;
+ int codec;
+
+ void string_toupper(std::string &str);
+ void patch_chars(std::string &str);
+ std::string path_external(void);
+
+public:
+ cTrackInfo(void);
+
+ void clear(void);
+
+ void set_recpath(const std::string &_recpath);
+ void set_path(int _file_pattern, int _upper);
+ void set_path(const std::string &_path);
+ void set_date(const char *_date);
+ void set_time(const char *_time);
+ void set_artist(const char *_artist);
+ void set_title(const char *_title);
+ void set_album(const char *_album);
+ void set_track(int _track);
+ void set_year(int _year);
+ void set_genre(const char *_genre);
+ void set_comment(const char *_comment);
+ void set_channel(const std::string &_channel);
+ void set_event(const std::string &_event);
+ void set_codec(int _codec);
+ void set_bit_rates(int _mpa_frame_bit_rate, int _bit_rate);
+
+ std::string get_recpath(void) const { return recpath; }
+ std::string get_path(void) const { return path; }
+ std::string get_partial_path(void) const { return partial_path ; }
+ std::string get_date(void) const { return recdate; }
+ std::string get_time(void) const { return rectime; }
+ std::string get_artist(void) const { return artist; }
+ std::string get_title(void) const { return title; }
+ std::string get_album(void) const { return album; }
+ int get_track(void) const { return track; }
+ int get_year(void) const { return year; }
+ std::string get_genre(void) const { return genre; }
+ std::string get_comment(void) const { return comment; }
+ std::string get_channel(void) const { return channel; }
+ std::string get_event(void) const { return event; }
+
+ int get_codec(void) const { return codec; }
+
+/* std::string get_codec(void) const { return codec; }
+ std::string get_codec_translated(void) const { return codec_translated; }
+*/
+ std::string get_recdir(void) const { return recdir; }
+};
+#endif /* __TRACKINFO_H */
diff --git a/xml-base.c b/xml-base.c
new file mode 100644
index 0000000..9900551
--- /dev/null
+++ b/xml-base.c
@@ -0,0 +1,104 @@
+/*
+ * xml-base.c
+ */
+
+#include "xml-base.h"
+
+#include <vdr/tools.h>
+
+#include <unistd.h>
+
+
+using namespace std;
+using namespace a_land;
+
+/* --- cXmlBase ------------------------------------------------------------- */
+
+cXmlBase::cXmlBase(const char *_root_element)
+{
+ root_element = _root_element;
+ document = NULL;
+ root = NULL;
+}
+
+
+cXmlBase::~cXmlBase()
+{
+ if (document) {
+ document->SaveFile();
+ }
+
+ delete document;
+}
+
+
+bool cXmlBase::load(const string &_path)
+{
+ if (document)
+ return true;
+
+ path = _path;
+
+ dsyslog("[audiorecorder]: loading xml-file (%s) (%s ,%s())",
+ path.c_str(), __FILE__, __func__);
+
+ document = new TiXmlDocument(path);
+
+ if (access(path.c_str(), F_OK) == -1) {
+ dsyslog("[audiorecorder]: creating empty xml-file (%s) (%s ,"
+ "%s())", path.c_str(), __FILE__, __func__);
+ TiXmlElement new_root(root_element);
+ document->InsertEndChild(new_root);
+ document->SaveFile();
+ }
+
+ if (! document->LoadFile()) {
+ dsyslog("[audiorecorder]: error while parsing xml-file (%s) "
+ "(%s, %s())", path.c_str(), __FILE__, __func__);
+ dsyslog("[audiorecorder]: %s, row: %d, column: %d (%s, %s())",
+ document->ErrorDesc(), document->ErrorRow(),
+ document->ErrorCol(), __FILE__, __func__);
+ return false;
+ }
+
+ set_root();
+
+ if (! root || root->ValueStr() != root_element)
+ return false;
+
+ copy_to_objects();
+
+ return true;
+}
+
+
+void cXmlBase::clear(void)
+{
+ remove(path.c_str());
+
+ delete document;
+ document = new TiXmlDocument(path);
+
+ root = new TiXmlElement(root_element);
+}
+
+
+void cXmlBase::add_subelement(TiXmlElement &main_element, const char *name,
+ const string &text)
+{
+ if (! name || text.empty())
+ return;
+
+ TiXmlElement element(name);
+ TiXmlText txt(text);
+ element.InsertEndChild(txt);
+
+ main_element.InsertEndChild(element);
+}
+
+
+void cXmlBase::set_root(void)
+{
+ if (document)
+ root = document->RootElement();
+}
diff --git a/xml-base.h b/xml-base.h
new file mode 100644
index 0000000..4c63426
--- /dev/null
+++ b/xml-base.h
@@ -0,0 +1,37 @@
+/*
+ * xml-base.h
+ */
+
+#ifndef __XML_BASE_H
+#define __XML_BASE_H
+
+#include "tinyxml/tinyxml.h"
+
+#include <string>
+
+
+class cXmlBase {
+private:
+ std::string path, root_element;
+
+ a_land::TiXmlDocument *document;
+ a_land::TiXmlElement *root;
+protected:
+ virtual ~cXmlBase();
+
+ virtual void copy_to_objects(void) {}
+public:
+ cXmlBase(const char *_root_element);
+
+ bool load(const std::string &_path);
+ void clear(void);
+
+ void add_subelement(a_land::TiXmlElement &main_element,
+ const char *name, const std::string &text);
+
+ a_land::TiXmlDocument *get_document(void) { return document; }
+ a_land::TiXmlElement *get_root(void) { return root; }
+ void set_root(void);
+};
+
+#endif /* __XML_BASE_H */
diff --git a/xml-cache.c b/xml-cache.c
new file mode 100644
index 0000000..14204b9
--- /dev/null
+++ b/xml-cache.c
@@ -0,0 +1,280 @@
+/*
+ * xml-cache.c
+ */
+
+#include "xml-cache.h"
+#include "cache.h"
+#include "audiorecorder.h"
+
+#include <taglib/mpegfile.h>
+#include <taglib/tag.h>
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <iostream>
+
+#include "setup.h"
+
+
+using namespace std;
+using namespace a_land;
+
+/* --- cXmlCache ------------------------------------------------------------ */
+
+
+cXmlCache::cXmlCache(void)
+:cXmlBase("cache") {}
+
+
+void cXmlCache::ReadDir(int level, std::string path)
+{
+ DIR *dir;
+ struct dirent entry, *result;
+ struct stat attribute;
+ string fullpath;
+ string file;
+
+ dsyslog("[audiorecorder]: level : %d - %s (%s, %s())", level, path.c_str(), __FILE__, __func__);
+
+
+ dir = opendir(path.c_str());
+ if (! dir)
+ return;
+
+
+ for (readdir_r(dir, &entry, &result); result; readdir_r(dir, &entry, &result)) {
+ file = result->d_name;
+
+ int len = file.length() - 8;
+ if (len < 0)
+ len = 0;
+
+ if (file == "." || file == ".." ||
+ file.substr(len, 8) == ".tmp.mp2")
+ continue;
+
+ fullpath = path;
+
+ string::reverse_iterator i = fullpath.rbegin();
+
+ if (*i != '/') {
+ fullpath.append("/");
+ }
+ fullpath.append(file);
+
+ stat(fullpath.c_str(), &attribute);
+
+ if (! attribute.st_mode & S_IFREG)
+ continue;
+
+ if ( attribute.st_mode & S_IFDIR) {
+ ReadDir(level + 1, fullpath);
+ } else {
+ for (int i1 = 0; i1 < NUM_CODECS; i1++) {
+ std::string tmp1;
+ tmp1 = ".";
+ tmp1.append(audio_codecs[i1]);
+ if (file.substr(file.length() - tmp1.length(), tmp1.length()) == tmp1) {
+ char date_str[11], time_str[6];
+ struct tm *tm_now, tm_store;
+ tm_now = localtime_r(&attribute.st_mtime, &tm_store);
+ strftime(date_str, 11, "%Y-%m-%d", tm_now);
+ strftime(time_str, 6, "%H.%M", tm_now);
+ rebuild_track(fullpath, date_str, time_str);
+ break;
+ }
+ }
+ }
+ }
+ closedir(dir);
+}
+
+void cXmlCache::rebuild(void)
+{
+ clear();
+
+ ReadDir(0, cPluginAudiorecorder::get_recdir().c_str());
+
+ get_document()->InsertEndChild(*get_root());
+
+ get_document()->SaveFile();
+
+ set_root();
+}
+
+
+void cXmlCache::add_track(const cTrackInfo &trackinfo, bool save)
+{
+ if (! get_root() || trackinfo.get_artist().empty() ||
+ trackinfo.get_title().empty())
+ return;
+
+ a_land::TiXmlElement track("track");
+ track.SetAttribute("path", trackinfo.get_partial_path());
+ track.SetAttribute("date", trackinfo.get_date());
+ track.SetAttribute("time", trackinfo.get_time());
+
+ add_subelement(track, "artist", trackinfo.get_artist());
+ add_subelement(track, "title", trackinfo.get_title());
+ add_subelement(track, "album", trackinfo.get_album());
+
+ stringstream tmp;
+ if (trackinfo.get_track() != 0)
+ tmp << trackinfo.get_track();
+
+ add_subelement(track, "tracknr", tmp.str());
+
+ tmp.str("");
+ tmp.clear();
+ if (trackinfo.get_year() != 0)
+ tmp << trackinfo.get_year();
+
+ add_subelement(track, "year", tmp.str());
+ add_subelement(track, "genre", trackinfo.get_genre());
+ add_subelement(track, "channel", trackinfo.get_channel());
+ add_subelement(track, "event", trackinfo.get_event());
+
+ get_root()->InsertEndChild(track);
+
+ if (save) {
+ get_document()->SaveFile();
+ }
+}
+
+
+void cXmlCache::copy_to_objects(void)
+{
+ a_land::TiXmlElement *xml_track = get_root()->FirstChildElement("track");
+
+ while (xml_track) {
+ cTrackInfo trackinfo;
+
+ string path = xml_track->Attribute("path");
+
+ if (path.empty()) {
+ /* remove deleted files from the xml-cache */
+ a_land::TiXmlElement *tmp = xml_track;
+ xml_track = xml_track->NextSiblingElement("track");
+ get_root()->RemoveChild(tmp);
+
+ continue;
+ }
+
+ path.insert(0, cPluginAudiorecorder::get_recdir());
+
+ trackinfo.set_path(path);
+
+ if (access(path.c_str(), F_OK) == -1) {
+ dsyslog("[audiorecorder]: copy %s : (%s, %s())", path.c_str(), __FILE__, __func__);
+ /* remove deleted files from the xml-cache */
+ a_land::TiXmlElement *tmp = xml_track;
+ xml_track = xml_track->NextSiblingElement("track");
+ get_root()->RemoveChild(tmp);
+
+ continue;
+ }
+
+ if (xml_track->Attribute("date"))
+ trackinfo.set_date(xml_track->Attribute("date"));
+ if (xml_track->Attribute("time"))
+ trackinfo.set_time(xml_track->Attribute("time"));
+
+ for (a_land::TiXmlElement *element = xml_track->FirstChildElement();
+ element; element = element->NextSiblingElement()) {
+ if (element->FirstChild() == NULL)
+ continue;
+ else if (element->ValueStr() == "artist")
+ trackinfo.set_artist(
+ element->FirstChild()->Value());
+ else if (element->ValueStr() == "title")
+ trackinfo.set_title(
+ element->FirstChild()->Value());
+ else if (element->ValueStr() == "album")
+ trackinfo.set_album(
+ element->FirstChild()->Value());
+ else if (element->ValueStr() == "tracknr")
+ trackinfo.set_track(
+ atoi(element->FirstChild()->Value()));
+ else if (element->ValueStr() == "year")
+ trackinfo.set_year(
+ atoi(element->FirstChild()->Value()));
+ else if (element->ValueStr() == "genre")
+ trackinfo.set_genre(
+ element->FirstChild()->Value());
+ else if (element->ValueStr() == "channel")
+ trackinfo.set_channel(
+ element->FirstChild()->Value());
+ else if (element->ValueStr() == "event")
+ trackinfo.set_event(
+ element->FirstChild()->Value());
+ }
+
+ if (! trackinfo.get_title().empty() &&
+ ! trackinfo.get_artist().empty())
+ Cache.add_track(trackinfo, false);
+
+ xml_track = xml_track->NextSiblingElement("track");
+ }
+ get_document()->SaveFile();
+}
+
+
+void cXmlCache::rebuild_track(const string &path, const char *date,
+ const char *time)
+{
+ if (cPluginAudiorecorder::get_dbg_level() > 1)
+ cout << "rebuilding track: " << path << endl;
+
+ TagLib::MPEG::File file(path.c_str());
+ TagLib::Tag *tag = file.tag();
+
+ if (! tag)
+ return;
+
+ cTrackInfo trackinfo;
+
+ trackinfo.set_path(path);
+ trackinfo.set_date(date);
+ trackinfo.set_time(time);
+
+ if (! tag->artist().isEmpty())
+ trackinfo.set_artist(tag->artist().toCString());
+
+ if (! tag->title().isEmpty())
+ trackinfo.set_title(tag->title().toCString());
+
+ if (! tag->album().isEmpty())
+ trackinfo.set_album(tag->album().toCString());
+
+ if (tag->track() != 0)
+ trackinfo.set_track(tag->track());
+
+ if (tag->year() != 0)
+ trackinfo.set_year(tag->year());
+
+ if (! tag->genre().isEmpty())
+ trackinfo.set_genre(tag->genre().toCString());
+
+ if (! tag->comment().isEmpty()) {
+ string com = tag->comment().toCString();
+ trackinfo.set_comment(com.c_str());
+ string::size_type f1 = com.find("recorded on \"");
+ string::size_type f2 = com.find("(vdr-audiorecorder ");
+ if (f1 != string::npos && f2 != string::npos) {
+ com.erase(0, 13);
+ string::size_type p1 = com.find_last_of('@');
+ string::size_type p2 = com.find_last_of('"');
+ if (p1 != string::npos) {
+ trackinfo.set_event(com.substr(0, p1));
+ if (p2 != string::npos)
+ trackinfo.set_channel(com.substr(p1 + 1,
+ p2 - p1 - 1));
+ }
+ }
+ }
+
+ Cache.add_track(trackinfo, false);
+ add_track(trackinfo, false);
+}
diff --git a/xml-cache.h b/xml-cache.h
new file mode 100644
index 0000000..9f364bf
--- /dev/null
+++ b/xml-cache.h
@@ -0,0 +1,33 @@
+/*
+ * xml-cache.h
+ */
+
+#ifndef __XML_CACHE_H
+#define __XML_CACHE_H
+
+#include "xml-base.h"
+#include "trackinfo.h"
+
+#include <unistd.h>
+
+#include <string>
+
+#include <iostream>
+
+
+
+class cXmlCache: public cXmlBase {
+private:
+ void rebuild_track(const std::string &path, const char *date,
+ const char *time);
+protected:
+ virtual void copy_to_objects(void);
+public:
+ cXmlCache(void);
+
+ void ReadDir(int level, std::string path);
+ void rebuild(void);
+ void add_track(const cTrackInfo &trackinfo, bool save = true);
+};
+
+#endif /* __XML_CACHE_H */