pax_global_header00006660000000000000000000000064126177434000014516gustar00rootroot0000000000000052 comment=2f0f90568e21dcfa49a11524c49390b31823afbc minidlna-1.1.5+dfsg/000077500000000000000000000000001261774340000142345ustar00rootroot00000000000000minidlna-1.1.5+dfsg/.gitignore000066400000000000000000000007101261774340000162220ustar00rootroot00000000000000ABOUT-NLS INSTALL Makefile.in Makefile aclocal.m4 autom4te.cache/ config.guess config.h.in config.rpath config.sub config.status config.log config.h confdefs.h conftest.* configure depcomp install-sh m4/ missing mkinstalldirs po/Makefile.in.in po/Makevars.template po/Rules-quot po/boldquot.sed po/en@boldquot.header po/en@quot.header po/insert-header.sin po/quot.sed po/remove-potcdate.sin po/Makefile po/POTFILES po/*.gmo po/stamp-po stamp-h1 .deps/ *.o minidlna-1.1.5+dfsg/AUTHORS000066400000000000000000000000601261774340000153000ustar00rootroot00000000000000Justin Maggard minidlna-1.1.5+dfsg/COPYING000066400000000000000000000437641261774340000153050ustar00rootroot00000000000000MiniDLNA is distributed under version 2 of the General Public License (included in its entirety, below). Version 2 is the only version of this license which this version of MiniDLNA (or modified versions derived from this one) may be distributed under. ------------------------------------------------------------------------ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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. Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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. , 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 Lesser General Public License instead of this License. minidlna-1.1.5+dfsg/ChangeLog000066400000000000000000000000001261774340000157740ustar00rootroot00000000000000minidlna-1.1.5+dfsg/LICENCE.miniupnpd000066400000000000000000000026771261774340000172370ustar00rootroot00000000000000Copyright (c) 2006-2007, Thomas BERNARD All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. minidlna-1.1.5+dfsg/Makefile.am000066400000000000000000000041351261774340000162730ustar00rootroot00000000000000# This file is part of MiniDLNA. # # MiniDLNA is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # # MiniDLNA 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 Lesser General Public # License along with MiniDLNA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA. AM_CFLAGS = -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 @STATIC_CFLAGS@ SUBDIRS=po sbin_PROGRAMS = minidlnad check_PROGRAMS = testupnpdescgen minidlnad_SOURCES = minidlna.c upnphttp.c upnpdescgen.c upnpsoap.c \ upnpreplyparse.c minixml.c clients.c \ getifaddr.c process.c upnpglobalvars.c \ options.c minissdp.c uuid.c upnpevents.c \ sql.c utils.c metadata.c scanner.c inotify.c \ tivo_utils.c tivo_beacon.c tivo_commands.c \ playlist.c image_utils.c albumart.c log.c \ containers.c tagutils/tagutils.c #if NEED_VORBIS vorbisflag = -lvorbis #endif #if NEED_OGG flacoggflag = -logg #endif minidlnad_LDADD = \ @LIBJPEG_LIBS@ \ @LIBID3TAG_LIBS@ \ @LIBSQLITE3_LIBS@ \ @LIBAVFORMAT_LIBS@ \ @LIBAVUTIL_LIBS@ \ @LIBEXIF_LIBS@ \ @LIBINTL@ \ @LIBICONV@ \ -lFLAC $(flacoggflag) $(vorbisflag) minidlnad_LDFLAGS = @STATIC_LDFLAGS@ testupnpdescgen_SOURCES = testupnpdescgen.c upnpdescgen.c testupnpdescgen_LDADD = \ @LIBJPEG_LIBS@ \ @LIBID3TAG_LIBS@ \ @LIBSQLITE3_LIBS@ \ @LIBAVFORMAT_LIBS@ \ @LIBAVUTIL_LIBS@ \ @LIBEXIF_LIBS@ \ -lFLAC $(flacoggflag) $(vorbisflag) SUFFIXES = .tmpl . .tmpl: sed -e s@:SBINDIR:@${sbindir}@ <$< >$@ GENERATED_FILES = \ linux/minidlna.init.d.script TEMPLATES = \ linux/minidlna.init.d.script.tmpl CLEANFILES = $(GENERATED_FILES) ACLOCAL_AMFLAGS = -I m4 EXTRA_DIST = m4/ChangeLog $(TEMPLATES) noinst_DATA = $(GENERATED_FILES) minidlna-1.1.5+dfsg/NEWS000066400000000000000000000164311261774340000147400ustar00rootroot000000000000001.1.5 - Released 10-Sep-2015 -------------------------------- - Re-enable Samsung DCM10, which adds video bookmarks and "BasicView" support. - Allow SSDP M-SEARCH from other subnets. - Fix some nfo file character escaping. - Fix crash with >3 network interfaces. - Support rotation of monochrome JPEGs. - Handle cover art streams that come after the video stream. - Recognize new hard links with inotify. - Work around LG TV ordering bug. - Implement TiVo image PixelShape support. - Support thumbnail rotation. - Use "Album Artist" tag from AAC files. - Add Korean translations. - Fix handling of bad FLAC files. 1.1.4 - Released 26-Aug-2014 -------------------------------- - Add magic container infrastructure. - Add magic containers for 50 recent items for each category. - Fix bad null termination in AAC parsing. - Fix requests for the last byte of a file, which affected MKV playback on Philips TV's. - Support 64-bit time_t values. 1.1.3 - Released 05-June-2014 -------------------------------- - Enhance log level settings. - Fix Samsung browsing when root_container is set. - Add Clang compiling support. - Fix compiling on systems without iconv. - Add merge_media_dirs option, to revert to the old behavior. - Add Asus O!Play client support. - Fix Broken SSDP multicast membership addition. - Fix crash bug with an emtpy filter argument. - Accept SMI subtitles in addition to SRT. - Add BubbleUPnP detection and enable subtitle support. - Allow the user to specify an arbitrary root container. - Add libavcodec > 54 / libav 10 compatibility. - Get embedded cover art from video files with recent libavformat versions. - Disable Samsung DCM10 capability, as it breaks compatibility with new models. - Add subtitle support for NetFront™ Living Connect middleware-based clients. 1.1.2 - Released 06-Mar-2014 -------------------------------- - Show client status on our basic presentation page. - Add a new force_sort_criteria option, to globally override the SortCriteria value sent by the client. - Fix a couple resource leaks. - Add configuration include file support. - Support DLNA/UPnP-AV searches issued by clients using the Grilo framework. - Fix some clients playing artwork instead of movie. - Fix bookmarks on Samsung Series E clients. - Add an extra folder level if there are multiple media locations. - Fix some multicast membership issues with changing network settings. - Make max number of children (connections) configurable. - Fix choppy playback with some file types on Panasonic clients by increasing the max connection limit. 1.1.1 - Released 01-Nov-2013 -------------------------------- - Add network interface monitoring support on Linux. - Don't require a configured network interface to start up. - Fix some minor spec compliance issues. 1.1.0 - Released 04-April-2013 -------------------------------- - Add support for other operating systems. - Switch to autoconf from our handcrafted genconfig.sh. - Add configuration option for UUID. - Add configuration option to specify the user to run as. - Add support for limiting a media dir to multiple media types. - Force a rescan if we detect a new or missing media_dir entry. - Fix crash caused by certain TiVo clients. - Fix crash bug on video files with some ffmpeg library versions. - Add support for TiVo MPEG-TS files. - Add some logging and forking tweaks to work better with systemd. - Validate or escape user input to prevent SQL injection. - Add forced sorting support for Panasonic devices. 1.0.25 - Released 13-July-2012 -------------------------------- - Fix a couple crash bugs on malformed WAV files. - Forcibly tweak the model number for Xbox360 clients, or they might ignore us. - Enable all network interfaces by default if none were specified. - Add flag to force downscaled thumbnails rather than using embedded ones. - Add DirecTV client detection, and fix image resolution issue. - Add support for the latest ffmpeg/libav library versions. - Fix a potential crash on requests for a resize of a non-existent image. - Make DeviceID checking more permissive for Sagem Radio. 1.0.24 - Released 14-Feb-2012 -------------------------------- - Fix playlist browsing with no SortOrder specified. - Fix inotify detection of caption file removal. - Handle an empty DeviceID from Zyxel media player SOAP request. - Fix false positives in playlist caching optimization when we have duplicate file names in different directories. - Trim the camera model name extracted from EXIF tags. - Add support for user-configurable log level settings. - Add DLNA.ORG_FLAGS support. 1.0.23 - Released 23-Jan-2012 -------------------------------- - Enable the subtitle menu on some Samsung TV's. - Add subtitle support for Panasonic TV's. - Add workarounds for LifeTab tablets' bad behavior. - Speed up playlist parsing. - Make metadata-based virtual containers case insensitive. - Add folder art support (very few clients support this though). - Improve trimming of quotation marks. - Fix SRT caption support with the latest Samsung Series D firmware. - Fix subtitles on LG TV's for items whose titles don't have a dot in them. - Add support for the av:mediaClass tag, so some Sony devices can filter items by media type. - Fix inotify detection issues on first-level folders. - Work around LifeTab's broken DLNA support. - Add image rotation support during resize. (mostly useful for TiVo) 1.0.22 - Released 24-Aug-2011 -------------------------------- - Add bookmark support for some Samsung TV's. - Fix a memory leak when certain model Samsung TV's or Roku devices are on the network. - Fix detection of Samsung Series D models. - Add WAV MIME workaround for Marantz Receivers and Roku SoundBridge. - Fix bitrate displayed on Microsoft PFS devices. - Fix a scanner crash when trying to scan image files with no read access. 1.0.21 - Released 18-July-2011 -------------------------------- - Fix a few issues with new libav/ffmpeg versions. - Fix FF/REW of AVI files on Samsung Series B TV's. - Fix a crash bug when playing music on TiVo. - Add the ability to change the root media container. - Add WAV/RIFF INFO tag parsing support for the most common tags. - Fix a crash bug with clients that request a large number of results. 1.0.20 - Released 09-June-2011 -------------------------------- - Fix a crash bug when scanning MPEG-TS files with odd packet sizes. - Fix AVI file streaming on Samsung A-Series TV's. - Improve support for the NETGEAR Digital Entertainer Live (EVA2000). - Add support for multiple network interfaces. - Add subtitle support for LG TV's and Blu-Ray players. - Fix some minor coding issues found by cppcheck. - Add client adaptation support for Toshiba Regza TV's. - Send known audio-only devices straight to the Music section on root requests. - Add client adaptation support for Roku SoundBridge audio clients. - Improve Sony client adaptation to allow support for more file types. - Add support for reading tags from MP4 video files with recent lavf versions. - Add support for Samsung's GetFeatureList method. 1.0.19 - Released 11-Mar-2011 -------------------------------- - When called with -R, only remove art_cache and files.db in case users use an imporant directory as their db dir. - Properly scan newly created directory symlinks. - Improve Windows 7 interoperability. - Add basic NLS support, so clients can display localized strings. - Optimize JPEG scaling by downscaling as much as possible during decompression. minidlna-1.1.5+dfsg/README000066400000000000000000000017601261774340000151200ustar00rootroot00000000000000MiniDLNA project (c) 2009 Justin Maggard Portions (c) 2006-2007 Thomas Bernard webpage: http://sourceforge.net/projects/minidlna/ This directory contains the MiniDLNA daemon software. This software is subject to the conditions detailed in the LICENCE file provided with this distribution. Parts of the software including the discovery code are licensed under the BSD revised license which is detailed in the LICENSE.miniupnpd file provided with the distribution. More information on MiniUPnPd can be found at http://miniupnp.free.fr. The MiniDLNA daemon is an UPnP-A/V and DLNA service which serves multimedia content to compatible clients on the network. See http://www.upnp.org/ for more details on UPnP and http://www.dlna.org/ for mode details on DLNA. See the INSTALL file for instructions on compiling, installing, and configuring minidlna. Prerequisites ================== - libexif - libjpeg - libid3tag - libFLAC - libvorbis - libsqlite3 - libavformat (the ffmpeg libraries) Justin Maggard minidlna-1.1.5+dfsg/TODO000066400000000000000000000002331261774340000147220ustar00rootroot00000000000000Things left to do: * Persistent HTTP connection (Keep-Alive) support * PNG image support * SortCriteria support * Upload support Wishlist: * Transcoding minidlna-1.1.5+dfsg/albumart.c000066400000000000000000000206441261774340000162150ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2008 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "upnpglobalvars.h" #include "albumart.h" #include "sql.h" #include "utils.h" #include "image_utils.h" #include "log.h" static int art_cache_exists(const char *orig_path, char **cache_file) { if( xasprintf(cache_file, "%s/art_cache%s", db_path, orig_path) < 0 ) return 0; strcpy(strchr(*cache_file, '\0')-4, ".jpg"); return (!access(*cache_file, F_OK)); } static char * save_resized_album_art(image_s *imsrc, const char *path) { int dstw, dsth; image_s *imdst; char *cache_file; char cache_dir[MAXPATHLEN]; if( !imsrc ) return NULL; if( art_cache_exists(path, &cache_file) ) return cache_file; strncpyt(cache_dir, cache_file, sizeof(cache_dir)); make_dir(dirname(cache_dir), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); if( imsrc->width > imsrc->height ) { dstw = 160; dsth = (imsrc->height<<8) / ((imsrc->width<<8)/160); } else { dstw = (imsrc->width<<8) / ((imsrc->height<<8)/160); dsth = 160; } imdst = image_resize(imsrc, dstw, dsth); if( !imdst ) { free(cache_file); return NULL; } cache_file = image_save_to_jpeg_file(imdst, cache_file); image_free(imdst); return cache_file; } /* And our main album art functions */ void update_if_album_art(const char *path) { char *dir; char *match; char file[MAXPATHLEN]; char fpath[MAXPATHLEN]; char dpath[MAXPATHLEN]; int ncmp = 0; int album_art; DIR *dh; struct dirent *dp; enum file_types type = TYPE_UNKNOWN; int64_t art_id = 0; int ret; strncpyt(fpath, path, sizeof(fpath)); match = basename(fpath); /* Check if this file name matches a specific audio or video file */ if( ends_with(match, ".cover.jpg") ) { ncmp = strlen(match)-10; } else { ncmp = strrchr(match, '.') - match; } /* Check if this file name matches one of the default album art names */ album_art = is_album_art(match); strncpyt(dpath, path, sizeof(dpath)); dir = dirname(dpath); dh = opendir(dir); if( !dh ) return; while ((dp = readdir(dh)) != NULL) { if (is_reg(dp) == 1) { type = TYPE_FILE; } else if (is_dir(dp) == 1) { type = TYPE_DIR; } else { snprintf(file, sizeof(file), "%s/%s", dir, dp->d_name); type = resolve_unknown_type(file, ALL_MEDIA); } if( type != TYPE_FILE ) continue; if( (dp->d_name[0] != '.') && (is_video(dp->d_name) || is_audio(dp->d_name)) && (album_art || strncmp(dp->d_name, match, ncmp) == 0) ) { DPRINTF(E_DEBUG, L_METADATA, "New file %s looks like cover art for %s\n", path, dp->d_name); snprintf(file, sizeof(file), "%s/%s", dir, dp->d_name); art_id = find_album_art(file, NULL, 0); ret = sql_exec(db, "UPDATE DETAILS set ALBUM_ART = %lld where PATH = '%q'", (long long)art_id, file); if( ret != SQLITE_OK ) DPRINTF(E_WARN, L_METADATA, "Error setting %s as cover art for %s\n", match, dp->d_name); } } closedir(dh); } char * check_embedded_art(const char *path, uint8_t *image_data, int image_size) { int width = 0, height = 0; char *art_path = NULL; char *cache_dir; FILE *dstfile; image_s *imsrc; static char last_path[PATH_MAX]; static unsigned int last_hash = 0; static int last_success = 0; unsigned int hash; if( !image_data || !image_size || !path ) { return NULL; } /* If the embedded image matches the embedded image from the last file we * checked, just make a hard link. Better than storing it on the disk twice. */ hash = DJBHash(image_data, image_size); if( hash == last_hash ) { if( !last_success ) return NULL; art_cache_exists(path, &art_path); if( link(last_path, art_path) == 0 ) { return(art_path); } else { if( errno == ENOENT ) { cache_dir = strdup(art_path); make_dir(dirname(cache_dir), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); free(cache_dir); if( link(last_path, art_path) == 0 ) return(art_path); } DPRINTF(E_WARN, L_METADATA, "Linking %s to %s failed [%s]\n", art_path, last_path, strerror(errno)); free(art_path); art_path = NULL; } } last_hash = hash; imsrc = image_new_from_jpeg(NULL, 0, image_data, image_size, 1, ROTATE_NONE); if( !imsrc ) { last_success = 0; return NULL; } width = imsrc->width; height = imsrc->height; if( width > 160 || height > 160 ) { art_path = save_resized_album_art(imsrc, path); } else if( width > 0 && height > 0 ) { size_t nwritten; if( art_cache_exists(path, &art_path) ) goto end_art; cache_dir = strdup(art_path); make_dir(dirname(cache_dir), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); free(cache_dir); dstfile = fopen(art_path, "w"); if( !dstfile ) { free(art_path); art_path = NULL; goto end_art; } nwritten = fwrite((void *)image_data, 1, image_size, dstfile); fclose(dstfile); if( nwritten != image_size ) { DPRINTF(E_WARN, L_METADATA, "Embedded art error: wrote %lu/%d bytes\n", (unsigned long)nwritten, image_size); remove(art_path); free(art_path); art_path = NULL; goto end_art; } } end_art: image_free(imsrc); if( !art_path ) { DPRINTF(E_WARN, L_METADATA, "Invalid embedded album art in %s\n", basename((char *)path)); last_success = 0; return NULL; } DPRINTF(E_DEBUG, L_METADATA, "Found new embedded album art in %s\n", basename((char *)path)); last_success = 1; strcpy(last_path, art_path); return(art_path); } static char * check_for_album_file(const char *path) { char file[MAXPATHLEN]; char mypath[MAXPATHLEN]; struct album_art_name_s *album_art_name; image_s *imsrc = NULL; int width=0, height=0; char *art_file, *p; const char *dir; struct stat st; int ret; if( stat(path, &st) != 0 ) return NULL; if( S_ISDIR(st.st_mode) ) { dir = path; goto check_dir; } strncpyt(mypath, path, sizeof(mypath)); dir = dirname(mypath); /* First look for file-specific cover art */ snprintf(file, sizeof(file), "%s.cover.jpg", path); ret = access(file, R_OK); if( ret != 0 ) { strncpyt(file, path, sizeof(file)); p = strrchr(file, '.'); if( p ) { strcpy(p, ".jpg"); ret = access(file, R_OK); } if( ret != 0 ) { p = strrchr(file, '/'); if( p ) { memmove(p+2, p+1, file+MAXPATHLEN-p-2); p[1] = '.'; ret = access(file, R_OK); } } } if( ret == 0 ) { if( art_cache_exists(file, &art_file) ) goto existing_file; free(art_file); imsrc = image_new_from_jpeg(file, 1, NULL, 0, 1, ROTATE_NONE); if( imsrc ) goto found_file; } check_dir: /* Then fall back to possible generic cover art file names */ for( album_art_name = album_art_names; album_art_name; album_art_name = album_art_name->next ) { snprintf(file, sizeof(file), "%s/%s", dir, album_art_name->name); if( access(file, R_OK) == 0 ) { if( art_cache_exists(file, &art_file) ) { existing_file: return art_file; } free(art_file); imsrc = image_new_from_jpeg(file, 1, NULL, 0, 1, ROTATE_NONE); if( !imsrc ) continue; found_file: width = imsrc->width; height = imsrc->height; if( width > 160 || height > 160 ) art_file = save_resized_album_art(imsrc, file); else art_file = strdup(file); image_free(imsrc); return(art_file); } } return NULL; } int64_t find_album_art(const char *path, uint8_t *image_data, int image_size) { char *album_art = NULL; int64_t ret = 0; if( (image_size && (album_art = check_embedded_art(path, image_data, image_size))) || (album_art = check_for_album_file(path)) ) { ret = sql_get_int_field(db, "SELECT ID from ALBUM_ART where PATH = '%q'", album_art); if( !ret ) { if( sql_exec(db, "INSERT into ALBUM_ART (PATH) VALUES ('%q')", album_art) == SQLITE_OK ) ret = sqlite3_last_insert_rowid(db); } } free(album_art); return ret; } minidlna-1.1.5+dfsg/albumart.h000066400000000000000000000017621261774340000162220ustar00rootroot00000000000000/* Album art extraction, caching, and scaling * * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Justin Maggard * * MiniDLNA media server * Copyright (C) 2008 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #ifndef __ALBUMART_H__ #define __ALBUMART_H__ void update_if_album_art(const char *path); int64_t find_album_art(const char *path, uint8_t *image_data, int image_size); #endif minidlna-1.1.5+dfsg/autogen.sh000077500000000000000000000030321261774340000162330ustar00rootroot00000000000000#!/bin/sh # This file is part of MiniDLNA. # # MiniDLNA is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # # MiniDLNA 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 Lesser General Public # License along with MiniDLNA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA. package="minidlna" srcdir=`dirname $0` test -z "$srcdir" && srcdir=. cd "$srcdir" DIE=0 (autoconf --version) < /dev/null > /dev/null 2>&1 || { echo echo "You must have autoconf installed to compile $package." echo "Download the appropriate package for your system," echo "or get the source from one of the GNU ftp sites" echo "listed in http://www.gnu.org/order/ftp.html" DIE=1 } (automake --version) < /dev/null > /dev/null 2>&1 || { echo echo "You must have automake installed to compile $package." echo "Download the appropriate package for your system," echo "or get the source from one of the GNU ftp sites" echo "listed in http://www.gnu.org/order/ftp.html" DIE=1 } if test "$DIE" -eq 1; then exit 1 fi echo "Generating configuration files for $package, please wait...." autoreconf -vfi minidlna-1.1.5+dfsg/clients.c000066400000000000000000000143521261774340000160460ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2013 NETGEAR * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include #include #include #include "clients.h" #include "getifaddr.h" #include "log.h" struct client_type_s client_types[] = { { 0, 0, "Unknown", NULL, EMatchNone }, { EXbox, FLAG_MIME_AVI_AVI | FLAG_MS_PFS, "Xbox 360", "Xbox/", EUserAgent }, { EPS3, FLAG_DLNA | FLAG_MIME_AVI_DIVX, "PLAYSTATION 3", "PLAYSTATION", EUserAgent }, { EPS3, FLAG_DLNA | FLAG_MIME_AVI_DIVX, "PLAYSTATION 3", "PLAYSTATION 3", EXAVClientInfo }, /* Samsung Series [CDE] BDPs and TVs must be separated, or some of our * advertised extra features trigger a folder browsing bug on BDPs. */ /* User-Agent: DLNADOC/1.50 SEC_HHP_BD-D5100/1.0 */ { ESamsungSeriesCDEBDP, FLAG_SAMSUNG | FLAG_DLNA | FLAG_NO_RESIZE, "Samsung Series [CDEF] BDP", "SEC_HHP_BD", EUserAgent }, /* User-Agent: DLNADOC/1.50 SEC_HHP_[TV]UE40D7000/1.0 */ /* User-Agent: DLNADOC/1.50 SEC_HHP_ Family TV/1.0 */ { ESamsungSeriesCDE, FLAG_SAMSUNG | FLAG_DLNA | FLAG_NO_RESIZE | FLAG_SAMSUNG_DCM10, "Samsung Series [CDEF]", "SEC_HHP_", EUserAgent }, { ESamsungSeriesA, FLAG_SAMSUNG | FLAG_DLNA | FLAG_NO_RESIZE, "Samsung Series A", "SamsungWiselinkPro", EUserAgent }, { ESamsungSeriesB, FLAG_SAMSUNG | FLAG_DLNA | FLAG_NO_RESIZE, "Samsung Series B", "Samsung DTV DMR", EModelName }, /* User-Agent: Panasonic MIL DLNA CP UPnP/1.0 DLNADOC/1.50 */ { EPanasonic, FLAG_DLNA | FLAG_FORCE_SORT, "Panasonic", "Panasonic", EUserAgent }, /* User-Agent: IPI/1.0 UPnP/1.0 DLNADOC/1.50 */ { ENetFrontLivingConnect, FLAG_DLNA | FLAG_FORCE_SORT | FLAG_CAPTION_RES, "NetFront Living Connect", "IPI/1", EUserAgent }, { EDenonReceiver, FLAG_DLNA, "Denon Receiver", "bridgeCo-DMP/3", EUserAgent }, { EFreeBox, FLAG_RESIZE_THUMBS, "FreeBox", "fbxupnpav/", EUserAgent }, { EPopcornHour, FLAG_MIME_FLAC_FLAC, "Popcorn Hour", "SMP8634", EUserAgent }, /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Blu-ray Disc Player"; mv="2.0" */ /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BLU-RAY HOME THEATRE SYSTEM"; mv="2.0"; */ /* Sony SMP-100 needs the same treatment as their BDP-S370 */ /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Media Player"; mv="2.0" */ { ESonyBDP, FLAG_DLNA, "Sony BDP", "mv=\"2.0\"", EXAVClientInfo }, /* User-Agent: Linux/2.6.31-1.0 UPnP/1.0 DLNADOC/1.50 INTEL_NMPR/2.0 LGE_DLNA_SDK/1.5.0 */ { ELGDevice, FLAG_DLNA | FLAG_CAPTION_RES, "LG", "LGE_DLNA_SDK", EUserAgent }, /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BRAVIA KDL-40EX503"; mv="1.7"; */ { ESonyBravia, FLAG_DLNA, "Sony Bravia", "BRAVIA", EXAVClientInfo }, /* X-AV-Client-Info: av=5.0; hn=""; cn="Sony Corporation"; mn="INTERNET TV NSX-40GT 1"; mv="0.1"; */ { ESonyInternetTV, FLAG_DLNA, "Sony Internet TV", "INTERNET TV", EXAVClientInfo }, { ENetgearEVA2000, FLAG_MS_PFS | FLAG_RESIZE_THUMBS, "EVA2000", "Verismo,", EUserAgent }, { EDirecTV, FLAG_RESIZE_THUMBS, "DirecTV", "DIRECTV ", EUserAgent }, { EToshibaTV, FLAG_DLNA, "Toshiba TV", "UPnP/1.0 DLNADOC/1.50 Intel_SDK_for_UPnP_devices/1.2", EUserAgent }, { ERokuSoundBridge, FLAG_MS_PFS | FLAG_AUDIO_ONLY | FLAG_MIME_WAV_WAV | FLAG_FORCE_SORT, "Roku SoundBridge", "Roku SoundBridge", EModelName }, { EMarantzDMP, FLAG_DLNA | FLAG_MIME_WAV_WAV, "marantz DMP", "marantz DMP", EFriendlyNameSSDP }, { EMediaRoom, FLAG_MS_PFS, "MS MediaRoom", "Microsoft-IPTV-Client", EUserAgent }, { ELifeTab, FLAG_MS_PFS, "LIFETAB", "LIFETAB", EFriendlyName }, { EAsusOPlay, FLAG_DLNA | FLAG_MIME_AVI_AVI | FLAG_CAPTION_RES, "Asus OPlay Mini/Mini+", "O!Play", EUserAgent }, { EBubbleUPnP, FLAG_CAPTION_RES, "BubbleUPnP", "BubbleUPnP", EUserAgent }, { EStandardDLNA150, FLAG_DLNA | FLAG_MIME_AVI_AVI, "Generic DLNA 1.5", "DLNADOC/1.50", EUserAgent }, { EStandardUPnP, 0, "Generic UPnP 1.0", "UPnP/1.0", EUserAgent }, { 0, 0, NULL, 0 } }; struct client_cache_s clients[CLIENT_CACHE_SLOTS]; struct client_cache_s * SearchClientCache(struct in_addr addr, int quiet) { int i; for (i = 0; i < CLIENT_CACHE_SLOTS; i++) { if (clients[i].addr.s_addr == addr.s_addr) { /* Invalidate this client cache if it's older than 1 hour */ if ((time(NULL) - clients[i].age) > 3600) { unsigned char mac[6]; if (get_remote_mac(addr, mac) == 0 && memcmp(mac, clients[i].mac, 6) == 0) { /* Same MAC as last time when we were able to identify the client, * so extend the timeout by another hour. */ clients[i].age = time(NULL); } else { memset(&clients[i], 0, sizeof(struct client_cache_s)); return NULL; } } if (!quiet) DPRINTF(E_DEBUG, L_HTTP, "Client found in cache. [%s/entry %d]\n", clients[i].type->name, i); return &clients[i]; } } return NULL; } struct client_cache_s * AddClientCache(struct in_addr addr, int type) { int i; for (i = 0; i < CLIENT_CACHE_SLOTS; i++) { if (clients[i].addr.s_addr) continue; get_remote_mac(addr, clients[i].mac); clients[i].addr = addr; clients[i].type = &client_types[type]; clients[i].age = time(NULL); DPRINTF(E_DEBUG, L_HTTP, "Added client [%s/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n", client_types[type].name, inet_ntoa(clients[i].addr), clients[i].mac[0], clients[i].mac[1], clients[i].mac[2], clients[i].mac[3], clients[i].mac[4], clients[i].mac[5], i); return &clients[i]; } return NULL; } minidlna-1.1.5+dfsg/clients.h000066400000000000000000000050671261774340000160560ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2013 NETGEAR * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #ifndef __CLIENTS_H__ #define __CLIENTS_H__ #include #include #include #define CLIENT_CACHE_SLOTS 25 /* Client capability/quirk flags */ #define FLAG_DLNA 0x00000001 #define FLAG_MIME_AVI_DIVX 0x00000002 #define FLAG_MIME_AVI_AVI 0x00000004 #define FLAG_MIME_FLAC_FLAC 0x00000008 #define FLAG_MIME_WAV_WAV 0x00000010 #define FLAG_RESIZE_THUMBS 0x00000020 #define FLAG_NO_RESIZE 0x00000040 #define FLAG_MS_PFS 0x00000080 /* Microsoft PlaysForSure client */ #define FLAG_SAMSUNG 0x00000100 #define FLAG_SAMSUNG_DCM10 0x00000200 #define FLAG_AUDIO_ONLY 0x00000400 #define FLAG_FORCE_SORT 0x00000800 #define FLAG_CAPTION_RES 0x00001000 /* Response-related flags */ #define FLAG_HAS_CAPTIONS 0x80000000 #define RESPONSE_FLAGS 0xF0000000 enum match_types { EMatchNone, EUserAgent, EXAVClientInfo, EFriendlyName, EModelName, EFriendlyNameSSDP }; enum client_types { EXbox = 1, EPS3, EDenonReceiver, EDirecTV, EFreeBox, ELGDevice, ELifeTab, EMarantzDMP, EMediaRoom, ENetgearEVA2000, EPanasonic, EPopcornHour, ERokuSoundBridge, ESamsungSeriesA, ESamsungSeriesB, ESamsungSeriesCDEBDP, ESamsungSeriesCDE, ESonyBDP, ESonyBravia, ESonyInternetTV, EToshibaTV, EAsusOPlay, EBubbleUPnP, ENetFrontLivingConnect, EStandardDLNA150, EStandardUPnP }; struct client_type_s { enum client_types type; uint32_t flags; const char *name; const char *match; enum match_types match_type; }; struct client_cache_s { struct in_addr addr; unsigned char mac[6]; struct client_type_s *type; time_t age; int connections; }; extern struct client_type_s client_types[]; extern struct client_cache_s clients[CLIENT_CACHE_SLOTS]; struct client_cache_s *SearchClientCache(struct in_addr addr, int quiet); struct client_cache_s *AddClientCache(struct in_addr addr, int type); #endif minidlna-1.1.5+dfsg/codelength.h000066400000000000000000000015131261774340000165210ustar00rootroot00000000000000/* Project : miniupnp * Author : Thomas BERNARD * copyright (c) 2005-2008 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENCE file. */ #ifndef __CODELENGTH_H__ #define __CODELENGTH_H__ /* Encode length by using 7bit per Byte : * Most significant bit of each byte specifies that the * following byte is part of the code */ #define DECODELENGTH(n, p) n = 0; \ do { n = (n << 7) | (*p & 0x7f); } \ while(*(p++)&0x80); #define CODELENGTH(n, p) if(n>=268435456) *(p++) = (n >> 28) | 0x80; \ if(n>=2097152) *(p++) = (n >> 21) | 0x80; \ if(n>=16384) *(p++) = (n >> 14) | 0x80; \ if(n>=128) *(p++) = (n >> 7) | 0x80; \ *(p++) = n & 0x7f; #endif minidlna-1.1.5+dfsg/configure.ac000066400000000000000000000524201261774340000165250ustar00rootroot00000000000000# This file is part of MiniDLNA. # # MiniDLNA is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # # MiniDLNA 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 Lesser General Public # License along with MiniDLNA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA. AC_INIT(MiniDLNA,1.1.3,,minidlna) #LT_INIT AC_CANONICAL_TARGET AM_INIT_AUTOMAKE([subdir-objects]) AC_CONFIG_HEADERS([config.h]) AM_SILENT_RULES([yes]) m4_ifdef([AC_USE_SYSTEM_EXTENSIONS], [AC_USE_SYSTEM_EXTENSIONS]) #MiniDLNA AM_ICONV AM_GNU_GETTEXT([external]) AM_GNU_GETTEXT_VERSION(0.18) # Checks for programs. AC_PROG_AWK AC_PROG_CC AM_PROG_CC_C_O AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET # Default string definitions AC_DEFINE_UNQUOTED([OS_URL],"http://sourceforge.net/projects/minidlna/",[OS URL]) AC_DEFINE_UNQUOTED([ROOTDEV_MANUFACTURER],"Justin Maggard", [Manufacturer]) AC_DEFINE_UNQUOTED([ROOTDEV_MANUFACTURERURL],"http://www.netgear.com/", [Manufacturer URL]) AC_DEFINE_UNQUOTED([ROOTDEV_MODELNAME],"Windows Media Connect compatible (MiniDLNA)", [Model name]) AC_DEFINE_UNQUOTED([ROOTDEV_MODELDESCRIPTION],"MiniDLNA on " OS_NAME, [Model description]) AC_DEFINE_UNQUOTED([ROOTDEV_MODELURL],OS_URL,[Model URL]) ################################################################################################################ # Checks for typedefs, structures, and compiler characteristics. AC_C_INLINE AC_TYPE_MODE_T AC_TYPE_OFF_T AC_TYPE_PID_T AC_TYPE_SIZE_T m4_ifdef([AC_TYPE_UINT8_T], [AC_TYPE_UINT8_T]) m4_ifdef([AC_TYPE_INT32_T], [AC_TYPE_INT32_T]) m4_ifdef([AC_TYPE_UINT32_T], [AC_TYPE_UINT32_T]) m4_ifdef([AC_TYPE_UINT64_T], [AC_TYPE_UINT64_T]) m4_ifdef([AC_TYPE_SSIZE_T], [AC_TYPE_SSIZE_T]) AC_STRUCT_DIRENT_D_TYPE AC_STRUCT_ST_BLOCKS AC_HEADER_STDBOOL AC_C_BIGENDIAN # Checks for library functions. AC_FUNC_FORK AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK AC_CHECK_FUNCS([gethostname getifaddrs gettimeofday inet_ntoa memmove memset mkdir realpath select sendfile setlocale socket strcasecmp strchr strdup strerror strncasecmp strpbrk strrchr strstr strtol strtoul]) # # Check for struct ip_mreqn # AC_MSG_CHECKING(for struct ip_mreqn) AC_TRY_COMPILE([#include ], [ struct ip_mreqn mreq; mreq.imr_address.s_addr = 0; ], [ # Yes, we have it... AC_MSG_RESULT(yes) AC_DEFINE([HAVE_STRUCT_IP_MREQN],[],[Support for struct ip_mreqn]) ], [ # We'll just have to try and use struct ip_mreq AC_MSG_RESULT(no) AC_MSG_CHECKING(for struct ip_mreq) AC_TRY_COMPILE([#include ], [ struct ip_mreq mreq; mreq.imr_interface.s_addr = 0; ], [ # Yes, we have it... AC_MSG_RESULT(yes) AC_DEFINE([HAVE_STRUCT_IP_MREQ],[],[Support for struct ip_mreq]) ], [ # No multicast support AC_MSG_ERROR([No multicast support]) ]) ]) ################################################################################################################ # Special include directories case $host in *-*-darwin*) DARWIN_OS=1 SEARCH_DIR="/opt/local" INCLUDE_DIR="$SEARCH_DIR/include" ;; *-*-solaris*) AC_DEFINE([SOLARIS], [1], [we are on solaris]) ;; *-*-cygwin*) CYGWIN_OS=1 ;; *-*-freebsd*) FREEBSD_OS=1 ;; *-*-openbsd*) OPENBSD_OS=1 ;; *-*-linux*) if test -f /etc/redhat-release; then INCLUDE_DIR="/usr/include/ffmpeg" fi ;; esac AC_CHECK_HEADERS(syscall.h sys/syscall.h mach/mach_time.h) AC_MSG_CHECKING([for __NR_clock_gettime syscall]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM( [ #include ], [ #ifndef __NR_clock_gettime #error #endif ] )], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_CLOCK_GETTIME_SYSCALL], [1], [Whether the __NR_clock_gettime syscall is defined]) ], [ AC_MSG_RESULT([no]) AC_SEARCH_LIBS([clock_gettime], [rt], [AC_DEFINE([HAVE_CLOCK_GETTIME], [1], [use clock_gettime])],) ]) AC_CHECK_HEADER(linux/netlink.h, [AC_DEFINE([HAVE_NETLINK],[1],[Support for Linux netlink])], [], [#include ]) ################################################################################################################ ### Library checks CPPFLAGS_SAVE="$CPPFLAGS" for dir in "" /usr/local $INCLUDE_DIR; do if test -n "$dir"; then CPPFLAGS="$CPPFLAGS_SAVE -I$dir" fi AC_CHECK_HEADERS([libavutil/avutil.h ffmpeg/libavutil/avutil.h libav/libavutil/avutil.h avutil.h ffmpeg/avutil.h libav/avutil.h], [HAVE_LIBAVUTIL=1]) if test -z "$HAVE_LIBAVUTIL"; then unset ac_cv_header_avutil_h unset ac_cv_header_libavutil_avutil_h unset ac_cv_header_ffmpeg_avutil_h unset ac_cv_header_ffmpeg_libavutil_avutil_h unset ac_cv_header_libav_avutil_h unset ac_cv_header_libav_libavutil_avutil_h continue fi break done if test -z "$HAVE_LIBAVUTIL"; then AC_MSG_ERROR([libavutil headers not found or not usable]) fi CPPFLAGS_SAVE="$CPPFLAGS" for dir in "" /usr/local $INCLUDE_DIR; do if test -n "$dir"; then CPPFLAGS="$CPPFLAGS_SAVE -I$dir" fi AC_CHECK_HEADERS([libavcodec/avcodec.h ffmpeg/libavcodec/avcodec.h libav/libavcodec/avcodec.h avcodec.h ffmpeg/avcodec.h libav/avcodec.h], [HAVE_LIBAVCODEC=1]) if test -z "$HAVE_LIBAVCODEC"; then unset ac_cv_header_avcodec_h unset ac_cv_header_libavcodec_avcodec_h unset ac_cv_header_ffmpeg_avcodec_h unset ac_cv_header_ffmpeg_libavcodec_avcodec_h unset ac_cv_header_libav_avcodec_h unset ac_cv_header_libav_libavcodec_avcodec_h continue fi break done if test -z "$HAVE_LIBAVCODEC"; then AC_MSG_ERROR([libavcodec headers not found or not usable]) fi CPPFLAGS_SAVE="$CPPFLAGS" for dir in "" /usr/local $INCLUDE_DIR; do if test -n "$dir"; then CPPFLAGS="$CPPFLAGS_SAVE -I$dir" fi AC_CHECK_HEADERS([libavformat/avformat.h ffmpeg/libavformat/avformat.h libav/libavformat/avformat.h avformat.h ffmpeg/avformat.h libav/avformat.h], [HAVE_LIBAVFORMAT=1]) if test -z "$HAVE_LIBAVFORMAT"; then unset ac_cv_header_avformat_h unset ac_cv_header_libavformat_avformat_h unset ac_cv_header_ffmpeg_avformat_h unset ac_cv_header_ffmpeg_libavformat_avformat_h unset ac_cv_header_libav_avformat_h unset ac_cv_header_libav_libavformat_avformat_h continue fi break done if test -z "$HAVE_LIBAVFORMAT"; then AC_MSG_ERROR([libavformat headers not found or not usable]) fi CPPFLAGS_SAVE="$CPPFLAGS" for dir in "" /usr/local $INCLUDE_DIR; do if test -n "$dir"; then CPPFLAGS="$CPPFLAGS -I$dir" fi AC_CHECK_HEADERS([jpeglib.h sqlite3.h libexif/exif-loader.h id3tag.h ogg/ogg.h vorbis/codec.h FLAC/metadata.h],,[unset $as_ac_Header; break],) if test x"$ac_cv_header_jpeglib_h" != x"yes"; then CPPFLAGS="$CPPFLAGS_SAVE" continue elif test x"$ac_cv_header_sqlite3_h" != x"yes"; then CPPFLAGS="$CPPFLAGS_SAVE" continue elif test x"$ac_cv_header_libexif_exif_loader_h" != x"yes"; then CPPFLAGS="$CPPFLAGS_SAVE" continue elif test x"$ac_cv_header_id3tag_h" != x"yes"; then CPPFLAGS="$CPPFLAGS_SAVE" continue elif test x"$ac_cv_header_ogg_ogg_h" != x"yes"; then CPPFLAGS="$CPPFLAGS_SAVE" continue elif test x"$ac_cv_header_vorbis_codec_h" != x"yes"; then CPPFLAGS="$CPPFLAGS_SAVE" continue elif test x"$ac_cv_header_FLAC_metadata_h" != x"yes"; then CPPFLAGS="$CPPFLAGS_SAVE" continue else break; fi done test x"$ac_cv_header_jpeglib_h" != x"yes" && AC_MSG_ERROR([libjpeg headers not found or not usable]) test x"$ac_cv_header_sqlite3_h" != x"yes" && AC_MSG_ERROR([libsqlite3 headers not found or not usable]) test x"$ac_cv_header_libexif_exif_loader_h" != x"yes" && AC_MSG_ERROR([libexif headers not found or not usable]) test x"$ac_cv_header_id3tag_h" != x"yes" && AC_MSG_ERROR([libid3tag headers not found or not usable]) test x"$ac_cv_header_ogg_ogg_h" != x"yes" && AC_MSG_ERROR([libogg headers not found or not usable]) test x"$ac_cv_header_vorbis_codec_h" != x"yes" && AC_MSG_ERROR([libvorbis headers not found or not usable]) test x"$ac_cv_header_FLAC_metadata_h" != x"yes" && AC_MSG_ERROR([libFLAC headers not found or not usable]) CFLAGS_SAVE="$CFLAGS" CFLAGS="$CFLAGS -Wall -Werror" AC_MSG_CHECKING([if we should use the daemon() libc function]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM( [ #include #include ], [ return daemon(0, 0); ] )], [ AC_DEFINE([USE_DAEMON], [1], [use the system's builtin daemon()]) AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) ]) AC_MSG_CHECKING([if scandir declaration requires const char cast]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM( [ #include #include #include ], [ int filter(struct dirent *d); struct dirent **ptr = NULL; char *name = NULL; (void)scandir(name, &ptr, filter, alphasort); ] )], [ AC_MSG_RESULT([no]) ], [ AC_DEFINE([SCANDIR_CONST], [1], [scandir needs const char cast]) AC_MSG_RESULT([yes]) ]) AC_MSG_CHECKING([for linux sendfile support]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM( [ #include #include ], [ int tofd = 0, fromfd = 0; off_t offset; size_t total = 0; ssize_t nwritten = sendfile(tofd, fromfd, &offset, total); return nwritten; ] )], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_LINUX_SENDFILE_API], [1], [Whether linux sendfile() API is available]) ], [ AC_MSG_RESULT([no]) ]) AC_MSG_CHECKING([for darwin sendfile support]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM( [ #include #include #include #include ], [ int fd = 0, s = 0; off_t offset = 0, len; struct sf_hdtr *hdtr = NULL; int flags = 0; int ret; ret = sendfile(fd, s, offset, &len, hdtr, flags); return ret; ] )], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_DARWIN_SENDFILE_API], [1], [Whether darwin sendfile() API is available]) ], [ AC_MSG_RESULT([no]) ]) AC_MSG_CHECKING([for freebsd sendfile support]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM( [ #include #include #include #include ], [ int fromfd=0, tofd=0, ret, total=0; off_t offset=0, nwritten; struct sf_hdtr hdr; struct iovec hdtrl; hdr.headers = &hdtrl; hdr.hdr_cnt = 1; hdr.trailers = NULL; hdr.trl_cnt = 0; hdtrl.iov_base = NULL; hdtrl.iov_len = 0; ret = sendfile(fromfd, tofd, offset, total, &hdr, &nwritten, 0); ] )], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_FREEBSD_SENDFILE_API], [1], [Whether freebsd sendfile() API is available]) ], [ AC_MSG_RESULT([no]) ]) CFLAGS="$CFLAGS_SAVE" LDFLAGS_SAVE="$LDFLAGS" for dir in "" /usr/local $SEARCH_DIR; do if test -n "$dir"; then LDFLAGS="$LDFLAGS -L$dir/lib" fi AC_CHECK_LIB([jpeg], [jpeg_set_defaults], [LIBJPEG_LIBS="-ljpeg"], [unset ac_cv_lib_jpeg_jpeg_set_defaults; LDFLAGS="$LDFLAGS_SAVE"; continue]) break done test x"$ac_cv_lib_jpeg_jpeg_set_defaults" = x"yes" || AC_MSG_ERROR([Could not find libjpeg]) AC_SUBST(LIBJPEG_LIBS) LDFLAGS_SAVE="$LDFLAGS" for dir in "" /usr/local $SEARCH_DIR; do if test -n "$dir"; then LDFLAGS="$LDFLAGS -L$dir/lib" fi AC_CHECK_LIB([exif], [exif_data_new_from_file], [LIBEXIF_LIBS="-lexif"], [unset ac_cv_lib_exif_exif_data_new_from_file; LDFLAGS="$LDFLAGS_SAVE"; continue]) break done test x"$ac_cv_lib_jpeg_jpeg_set_defaults" = x"yes" || AC_MSG_ERROR([Could not find libexif]) AC_SUBST(LIBEXIF_LIBS) LDFLAGS_SAVE="$LDFLAGS" for dir in "" /usr/local $SEARCH_DIR; do if test -n "$dir"; then LDFLAGS="$LDFLAGS -L$dir/lib" fi AC_CHECK_LIB([id3tag], [id3_file_open], [LIBID3TAG_LIBS="-lid3tag"], [unset ac_cv_lib_id3tag_id3_file_open; LDFLAGS="$LDFLAGS_SAVE"; continue]) break done test x"$ac_cv_lib_id3tag_id3_file_open" = x"yes" || AC_MSG_ERROR([Could not find libid3tag]) AC_SUBST(LIBID3TAG_LIBS) LDFLAGS_SAVE="$LDFLAGS" for dir in "" /usr/local $SEARCH_DIR; do if test -n "$dir"; then LDFLAGS="$LDFLAGS -L$dir/lib" fi AC_CHECK_LIB(sqlite3, sqlite3_open, [LIBSQLITE3_LIBS="-lsqlite3"], [unset ac_cv_lib_sqlite3_sqlite3_open; LDFLAGS="$LDFLAGS_SAVE"; continue]) AC_CHECK_LIB(sqlite3, sqlite3_malloc, [AC_DEFINE([HAVE_SQLITE3_MALLOC], [1], [Define to 1 if the sqlite3_malloc function exists.])]) AC_CHECK_LIB(sqlite3, sqlite3_prepare_v2, [AC_DEFINE([HAVE_SQLITE3_PREPARE_V2], [1], [Define to 1 if the sqlite3_prepare_v2 function exists.])]) break done test x"$ac_cv_lib_sqlite3_sqlite3_open" = x"yes" || AC_MSG_ERROR([Could not find libsqlite3]) AC_SUBST(LIBSQLITE3_LIBS) LDFLAGS_SAVE="$LDFLAGS" for dir in "" /usr/local $SEARCH_DIR; do if test -n "$dir"; then LDFLAGS="$LDFLAGS -L$dir/lib" fi AC_CHECK_LIB([avformat], [av_open_input_file], [LIBAVFORMAT_LIBS="-lavformat"], [AC_CHECK_LIB([avformat], [avformat_open_input], [LIBAVFORMAT_LIBS="-lavformat"], [unset ac_cv_lib_avformat_av_open_input_file; unset ac_cv_lib_avformat_avformat_open_input; LDFLAGS="$LDFLAGS_SAVE"; continue])]) break done if test x"$ac_cv_lib_avformat_av_open_input_file" != x"yes" && test x"$ac_cv_lib_avformat_avformat_open_input" != x"yes"; then AC_MSG_ERROR([Could not find libavformat - part of ffmpeg]) fi AC_SUBST(LIBAVFORMAT_LIBS) AC_CHECK_LIB(avutil ,[av_rescale_q], [LIBAVUTIL_LIBS="-lavutil"], [AC_MSG_ERROR([Could not find libavutil - part of ffmpeg])]) AC_SUBST(LIBAVUTIL_LIBS) AC_CHECK_LIB(pthread, pthread_create) # test if we have vorbisfile # prior versions had ov_open_callbacks in libvorbis, test that, too. AC_CHECK_LIB(vorbisfile, ov_open_callbacks, [AC_CHECK_HEADERS([vorbis/vorbisfile.h], AM_CONDITIONAL(HAVE_VORBISFILE, true) AC_DEFINE(HAVE_VORBISFILE,1,[Have vorbisfile]), AM_CONDITIONAL(HAVE_VORBISFILE, false) AC_DEFINE(HAVE_VORBISFILE,0,[lacking vorbisfile]))], AM_CONDITIONAL(HAVE_VORBISFILE, false), -lvorbis -logg) AC_CHECK_LIB(FLAC, FLAC__stream_decoder_init_stream, [AC_CHECK_HEADERS([FLAC/all.h], AM_CONDITIONAL(HAVE_FLAC, true) AC_DEFINE(HAVE_FLAC,1,[Have flac]), AM_CONDITIONAL(HAVE_FLAC, false))], AM_CONDITIONAL(HAVE_FLAC, false), -logg) # test without -logg to see whether we really need it (libflac can be without) AC_CHECK_LIB(FLAC, FLAC__stream_decoder_init_ogg_stream, AM_CONDITIONAL(HAVE_FLAC, true) AC_DEFINE(HAVE_FLAC,1,[Have flac]) AM_CONDITIONAL(NEED_OGG, false), [AM_CONDITIONAL(NEED_OGG, true)]) AC_CHECK_LIB(vorbisfile, vorbis_comment_query, AM_CONDITIONAL(NEED_VORBIS, false), AM_CONDITIONAL(NEED_VORBIS, true), -logg) ################################################################################################################ ### Header checks AC_CHECK_HEADERS([arpa/inet.h asm/unistd.h endian.h machine/endian.h fcntl.h libintl.h locale.h netdb.h netinet/in.h stddef.h stdlib.h string.h sys/file.h sys/inotify.h sys/ioctl.h sys/param.h sys/socket.h sys/time.h unistd.h]) AC_CHECK_FUNCS(inotify_init, AC_DEFINE(HAVE_INOTIFY,1,[Whether kernel has inotify support]), [ AC_MSG_CHECKING([for __NR_inotify_init syscall]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM( [ #include ], [ #ifndef __NR_inotify_init #error #endif ] )], [ AC_MSG_RESULT([yes]) AC_DEFINE(HAVE_INOTIFY,1,[Whether kernel has inotify support]) ], [ AC_MSG_RESULT([no]) ]) ]) ################################################################################################################ ### Build Options AC_ARG_WITH(log-path, AS_HELP_STRING([--with-log-path=PATH],[Default log path]), [with_logpath="$withval"],[with_logpath="/var/log"]) AC_DEFINE_UNQUOTED([DEFAULT_LOG_PATH],"${with_logpath}",[Log path]) AC_ARG_WITH(db-path, AS_HELP_STRING([--with-db-path=PATH],[Default DB path]), [with_dbpath="$withval"],[with_dbpath="/var/cache/minidlna"]) AC_DEFINE_UNQUOTED([DEFAULT_DB_PATH],"${with_dbpath}",[DB path]) AC_ARG_WITH(os-name, AS_HELP_STRING([--with-os-name=NAME],[OS Name]), [with_osname="$withval"],[with_osname="$(uname -s)"]) AC_DEFINE_UNQUOTED([OS_NAME],"${with_osname}",[OS Name]) AC_ARG_WITH(os-version, AS_HELP_STRING([--with-os-version=VERS],[OS Version]), [with_osver="$withval"],[with_osver="$(uname -r)"]) AC_DEFINE_UNQUOTED([OS_VERSION],"${with_osver}",[OS Version]) AC_ARG_WITH(os-url, AS_HELP_STRING([--with-os-url=URL],[OS URL]), [with_osurl="$withval"],[with_osurl="http://www.netgear.com"]) AC_DEFINE_UNQUOTED([OS_URL],"${with_osurl}",[OS URL]) AC_MSG_CHECKING([whether to enable TiVo support]) AC_ARG_ENABLE(tivo, [ --enable-tivo enable TiVo support],[ if test "$enableval" = "yes"; then AC_DEFINE(TIVO_SUPPORT, 1, [Define to 1 if you want to enable TiVo support]) AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) fi ],[ AC_MSG_RESULT([no]) ] ) AC_MSG_CHECKING([whether to enable generic NETGEAR device support]) AC_ARG_ENABLE(netgear, [ --enable-netgear enable generic NETGEAR device support],[ if test "$enableval" = "yes"; then AC_DEFINE([NETGEAR],[1],[Define to 1 if you want to enable generic NETGEAR device support]) AC_DEFINE_UNQUOTED([OS_URL],"http://www.netgear.com/") AC_DEFINE_UNQUOTED([ROOTDEV_MANUFACTURERURL],"http://www.netgear.com/") AC_DEFINE_UNQUOTED([ROOTDEV_MANUFACTURER],"NETGEAR") AC_DEFINE_UNQUOTED([ROOTDEV_MODELNAME],"Windows Media Connect compatible (ReadyDLNA)") AC_DEFINE_UNQUOTED([ROOTDEV_MODELDESCRIPTION],"ReadyDLNA") AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) fi ],[ AC_MSG_RESULT([no]) ] ) AC_MSG_CHECKING([whether to enable NETGEAR ReadyNAS support]) AC_ARG_ENABLE(readynas, [ --enable-readynas enable NETGEAR ReadyNAS support],[ if test "$enableval" = "yes"; then AC_DEFINE([NETGEAR],[1],[Define to 1 if you want to enable generic NETGEAR device support]) AC_DEFINE([READYNAS],[1],[Define to 1 if you want to enable NETGEAR ReadyNAS support]) AC_DEFINE([TIVO_SUPPORT], 1, [Define to 1 if you want to enable TiVo support]) AC_DEFINE([PNPX],[5],[Define to 5 if you want to enable NETGEAR ReadyNAS PnP-X support]) AC_DEFINE_UNQUOTED([OS_URL],"http://www.readynas.com/") AC_DEFINE_UNQUOTED([ROOTDEV_MANUFACTURERURL],"http://www.netgear.com/") AC_DEFINE_UNQUOTED([ROOTDEV_MANUFACTURER],"NETGEAR") AC_DEFINE_UNQUOTED([ROOTDEV_MODELNAME],"Windows Media Connect compatible (ReadyDLNA)") AC_DEFINE_UNQUOTED([ROOTDEV_MODELDESCRIPTION],"ReadyDLNA") AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) fi ],[ AC_MSG_RESULT([no]) ] ) AC_MSG_CHECKING([whether to build a static binary executable]) AC_ARG_ENABLE(static, [ --enable-static build a static binary executable],[ if test "$enableval" = "yes"; then STATIC_CFLAGS="-DSTATIC" AC_SUBST(STATIC_CFLAGS) STATIC_LDFLAGS="-Wl,-Bstatic" AC_SUBST(STATIC_LDFLAGS) AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) fi ],[ AC_MSG_RESULT([no]) ] ) case "$target_os" in darwin*) ;; freebsd*) VER=`grep '#define __FreeBSD_version' /usr/include/sys/param.h | awk '{print $3}'` OS_URL=http://www.freebsd.org/ ;; solaris*) AC_DEFINE([USE_IPF], [1], [Define to enable IPF]) AC_DEFINE([LOG_PERROR], [0], [Define to enable logging]) AC_DEFINE([SOLARIS_KSTATS], [1], [Define to enable Solaris Kernel Stats]) ;; kfreebsd*) OS_URL=http://www.debian.org/ ;; linux*) ;; openbsd*) OS_URL=http://www.openbsd.org/ ;; *) echo "Unknown OS : $target_os" ;; esac AC_OUTPUT([ po/Makefile.in Makefile ]) minidlna-1.1.5+dfsg/containers.c000066400000000000000000000127531261774340000165550ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2014 NETGEAR * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include #include "clients.h" #include "minidlnatypes.h" #include "scanner.h" #include "upnpglobalvars.h" #include "containers.h" #include "log.h" #define NINETY_DAYS "7776000" const char *music_id = MUSIC_ID; const char *music_all_id = MUSIC_ALL_ID; const char *music_genre_id = MUSIC_GENRE_ID; const char *music_artist_id = MUSIC_ARTIST_ID; const char *music_album_id = MUSIC_ALBUM_ID; const char *music_plist_id = MUSIC_PLIST_ID; const char *music_dir_id = MUSIC_DIR_ID; const char *video_all_id = VIDEO_ALL_ID; const char *video_dir_id = VIDEO_DIR_ID; const char *image_all_id = IMAGE_ALL_ID; const char *image_date_id = IMAGE_DATE_ID; const char *image_camera_id = IMAGE_CAMERA_ID; const char *image_dir_id = IMAGE_DIR_ID; struct magic_container_s magic_containers[] = { /* Alternate root container */ { NULL, "0", &runtime_vars.root_container, NULL, "0", NULL, NULL, NULL, NULL, -1, 0, }, /* Recent 50 audio items */ { "Recently Added", "1$FF0", NULL, "\"1$FF0$\" || OBJECT_ID", "\"1$FF0\"", "o.OBJECT_ID", "(select null from DETAILS where MIME glob 'a*' and timestamp > (strftime('%s','now') - "NINETY_DAYS") limit 50)", "MIME glob 'a*' and REF_ID is NULL and timestamp > (strftime('%s','now') - "NINETY_DAYS")", "order by TIMESTAMP DESC", 50, 0, }, /* Recent 50 video items */ { "Recently Added", "2$FF0", NULL, "\"2$FF0$\" || OBJECT_ID", "\"2$FF0\"", "o.OBJECT_ID", "(select null from DETAILS where MIME glob 'v*' and timestamp > (strftime('%s','now') - "NINETY_DAYS") limit 50)", "MIME glob 'v*' and REF_ID is NULL and timestamp > (strftime('%s','now') - "NINETY_DAYS")", "order by TIMESTAMP DESC", 50, 0, }, /* Recent 50 image items */ { "Recently Added", "3$FF0", NULL, "\"3$FF0$\" || OBJECT_ID", "\"3$FF0\"", "o.OBJECT_ID", "(select null from DETAILS where MIME glob 'i*' and timestamp > (strftime('%s','now') - "NINETY_DAYS") limit 50)", "MIME glob 'i*' and REF_ID is NULL and timestamp > (strftime('%s','now') - "NINETY_DAYS")", "order by TIMESTAMP DESC", 50, 0, }, /* Microsoft PlaysForSure containers */ { NULL, "4", &music_all_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, { NULL, "5", &music_genre_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, { NULL, "6", &music_artist_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, { NULL, "7", &music_album_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, { NULL, "8", &video_all_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, { NULL, "B", &image_all_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, { NULL, "C", &image_date_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, { NULL, "F", &music_plist_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, { NULL, "14", &music_dir_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, { NULL, "15", &video_dir_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, { NULL, "16", &image_dir_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, { NULL, "D2", &image_camera_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, /* Jump straight to Music on audio-only devices */ { NULL, "0", &music_id, NULL, "0", NULL, NULL, NULL, NULL, -1, FLAG_AUDIO_ONLY }, { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0 } }; struct magic_container_s * in_magic_container(const char *id, int flags, const char **real_id) { size_t len; int i; for (i = 0; magic_containers[i].objectid_match; i++) { if (magic_containers[i].required_flags && !(flags & magic_containers[i].required_flags)) continue; if (magic_containers[i].objectid && !(*magic_containers[i].objectid)) continue; DPRINTF(E_MAXDEBUG, L_HTTP, "Checking magic container %d [%s]\n", i, magic_containers[i].objectid_match); len = strlen(magic_containers[i].objectid_match); if (strncmp(id, magic_containers[i].objectid_match, len) == 0) { if (*(id+len) == '$') *real_id = id+len+1; else if (*(id+len) == '\0') *real_id = id; else continue; DPRINTF(E_DEBUG, L_HTTP, "Found magic container %d [%s]\n", i, magic_containers[i].objectid_match); return &magic_containers[i]; } } return NULL; } struct magic_container_s * check_magic_container(const char *id, int flags) { int i; for (i = 0; magic_containers[i].objectid_match; i++) { if (magic_containers[i].required_flags && !(flags & magic_containers[i].required_flags)) continue; if (magic_containers[i].objectid && !(*magic_containers[i].objectid)) continue; DPRINTF(E_MAXDEBUG, L_HTTP, "Checking magic container %d [%s]\n", i, magic_containers[i].objectid_match); if (strcmp(id, magic_containers[i].objectid_match) == 0) { DPRINTF(E_DEBUG, L_HTTP, "Found magic container %d [%s]\n", i, magic_containers[i].objectid_match); return &magic_containers[i]; } } return NULL; } minidlna-1.1.5+dfsg/containers.h000066400000000000000000000022331261774340000165520ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2014 NETGEAR * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ struct magic_container_s { const char *name; const char *objectid_match; const char **objectid; const char *objectid_sql; const char *parentid_sql; const char *refid_sql; const char *child_count; const char *where; const char *orderby; int max_count; int required_flags; }; extern struct magic_container_s magic_containers[]; struct magic_container_s *in_magic_container(const char *id, int flags, const char **real_id); struct magic_container_s *check_magic_container(const char *id, int flags); minidlna-1.1.5+dfsg/getifaddr.c000066400000000000000000000237151261774340000163410ustar00rootroot00000000000000/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * * Copyright (c) 2006, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #if defined(sun) #include #endif #include "config.h" #if HAVE_GETIFADDRS # include # ifdef __linux__ # ifndef AF_LINK # define AF_LINK AF_INET # endif # else # include # endif # ifndef IFF_SLAVE # define IFF_SLAVE 0 # endif #endif #ifdef HAVE_NETLINK # include # include #endif #include "upnpglobalvars.h" #include "getifaddr.h" #include "minissdp.h" #include "utils.h" #include "log.h" static int getifaddr(const char *ifname) { #if HAVE_GETIFADDRS struct ifaddrs *ifap, *p; struct sockaddr_in *addr_in; if (getifaddrs(&ifap) != 0) { DPRINTF(E_ERROR, L_GENERAL, "getifaddrs(): %s\n", strerror(errno)); return -1; } for (p = ifap; p != NULL; p = p->ifa_next) { if (!p->ifa_addr || p->ifa_addr->sa_family != AF_INET) continue; if (ifname && strcmp(p->ifa_name, ifname) != 0) continue; addr_in = (struct sockaddr_in *)p->ifa_addr; if (!ifname && (p->ifa_flags & (IFF_LOOPBACK | IFF_SLAVE))) continue; memcpy(&lan_addr[n_lan_addr].addr, &addr_in->sin_addr, sizeof(lan_addr[n_lan_addr].addr)); if (!inet_ntop(AF_INET, &addr_in->sin_addr, lan_addr[n_lan_addr].str, sizeof(lan_addr[0].str)) ) { DPRINTF(E_ERROR, L_GENERAL, "inet_ntop(): %s\n", strerror(errno)); continue; } addr_in = (struct sockaddr_in *)p->ifa_netmask; memcpy(&lan_addr[n_lan_addr].mask, &addr_in->sin_addr, sizeof(lan_addr[n_lan_addr].mask)); lan_addr[n_lan_addr].ifindex = if_nametoindex(p->ifa_name); lan_addr[n_lan_addr].snotify = OpenAndConfSSDPNotifySocket(&lan_addr[n_lan_addr]); if (lan_addr[n_lan_addr].snotify >= 0) n_lan_addr++; if (ifname || n_lan_addr >= MAX_LAN_ADDR) break; } freeifaddrs(ifap); if (ifname && !p) { DPRINTF(E_ERROR, L_GENERAL, "Network interface %s not found\n", ifname); return -1; } #else int s = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in addr; struct ifconf ifc; struct ifreq *ifr; char buf[8192]; int i, n; memset(&ifc, '\0', sizeof(ifc)); ifc.ifc_buf = buf; ifc.ifc_len = sizeof(buf); if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { DPRINTF(E_ERROR, L_GENERAL, "SIOCGIFCONF: %s\n", strerror(errno)); close(s); return -1; } n = ifc.ifc_len / sizeof(struct ifreq); for (i = 0; i < n; i++) { ifr = &ifc.ifc_req[i]; if (ifname && strcmp(ifr->ifr_name, ifname) != 0) continue; if (!ifname && (ioctl(s, SIOCGIFFLAGS, ifr) < 0 || ifr->ifr_ifru.ifru_flags & IFF_LOOPBACK)) continue; if (ioctl(s, SIOCGIFADDR, ifr) < 0) continue; memcpy(&addr, &(ifr->ifr_addr), sizeof(addr)); memcpy(&lan_addr[n_lan_addr].addr, &addr.sin_addr, sizeof(lan_addr[n_lan_addr].addr)); if (!inet_ntop(AF_INET, &addr.sin_addr, lan_addr[n_lan_addr].str, sizeof(lan_addr[0].str))) { DPRINTF(E_ERROR, L_GENERAL, "inet_ntop(): %s\n", strerror(errno)); close(s); continue; } if (ioctl(s, SIOCGIFNETMASK, ifr) < 0) continue; memcpy(&addr, &(ifr->ifr_addr), sizeof(addr)); memcpy(&lan_addr[n_lan_addr].mask, &addr.sin_addr, sizeof(addr)); lan_addr[n_lan_addr].ifindex = if_nametoindex(ifr->ifr_name); lan_addr[n_lan_addr].snotify = OpenAndConfSSDPNotifySocket(&lan_addr[n_lan_addr]); if (lan_addr[n_lan_addr].snotify >= 0) n_lan_addr++; if (ifname || n_lan_addr >= MAX_LAN_ADDR) break; } close(s); if (ifname && i == n) { DPRINTF(E_ERROR, L_GENERAL, "Network interface %s not found\n", ifname); return -1; } #endif return n_lan_addr; } int getsyshwaddr(char *buf, int len) { unsigned char mac[6]; int ret = -1; #if defined(HAVE_GETIFADDRS) && !defined (__linux__) && !defined (__sun__) struct ifaddrs *ifap, *p; struct sockaddr_in *addr_in; uint8_t a; if (getifaddrs(&ifap) != 0) { DPRINTF(E_ERROR, L_GENERAL, "getifaddrs(): %s\n", strerror(errno)); return -1; } for (p = ifap; p != NULL; p = p->ifa_next) { if (p->ifa_addr && p->ifa_addr->sa_family == AF_LINK) { addr_in = (struct sockaddr_in *)p->ifa_addr; a = (htonl(addr_in->sin_addr.s_addr) >> 0x18) & 0xFF; if (a == 127) continue; #if defined(__linux__) struct ifreq ifr; int fd; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) continue; strncpy(ifr.ifr_name, p->ifa_name, IFNAMSIZ); ret = ioctl(fd, SIOCGIFHWADDR, &ifr); close(fd); if (ret < 0) continue; memcpy(mac, ifr.ifr_hwaddr.sa_data, 6); #else struct sockaddr_dl *sdl; sdl = (struct sockaddr_dl*)p->ifa_addr; memcpy(mac, LLADDR(sdl), sdl->sdl_alen); #endif if (MACADDR_IS_ZERO(mac)) continue; ret = 0; break; } } freeifaddrs(ifap); #else struct if_nameindex *ifaces, *if_idx; struct ifreq ifr; int fd; memset(&mac, '\0', sizeof(mac)); /* Get the spatially unique node identifier */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) return ret; ifaces = if_nameindex(); if (!ifaces) { close(fd); return ret; } for (if_idx = ifaces; if_idx->if_index; if_idx++) { strncpyt(ifr.ifr_name, if_idx->if_name, IFNAMSIZ); if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) continue; if (ifr.ifr_ifru.ifru_flags & IFF_LOOPBACK) continue; if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) continue; #ifdef __sun__ if (MACADDR_IS_ZERO(ifr.ifr_addr.sa_data)) continue; memcpy(mac, ifr.ifr_addr.sa_data, 6); #else if (MACADDR_IS_ZERO(ifr.ifr_hwaddr.sa_data)) continue; memcpy(mac, ifr.ifr_hwaddr.sa_data, 6); #endif ret = 0; break; } if_freenameindex(ifaces); close(fd); #endif if (ret == 0) { if (len > 12) sprintf(buf, "%02x%02x%02x%02x%02x%02x", mac[0]&0xFF, mac[1]&0xFF, mac[2]&0xFF, mac[3]&0xFF, mac[4]&0xFF, mac[5]&0xFF); else if (len == 6) memmove(buf, mac, 6); } return ret; } int get_remote_mac(struct in_addr ip_addr, unsigned char *mac) { struct in_addr arp_ent; FILE * arp; char remote_ip[16]; int matches, hwtype, flags; memset(mac, 0xFF, 6); arp = fopen("/proc/net/arp", "r"); if (!arp) return 1; while (!feof(arp)) { matches = fscanf(arp, "%15s 0x%8X 0x%8X %2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", remote_ip, &hwtype, &flags, &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]); if (matches != 9) continue; inet_pton(AF_INET, remote_ip, &arp_ent); if (ip_addr.s_addr == arp_ent.s_addr) break; mac[0] = 0xFF; } fclose(arp); if (mac[0] == 0xFF) { memset(mac, 0xFF, 6); return 1; } return 0; } void reload_ifaces(int force_notify) { struct in_addr old_addr[MAX_LAN_ADDR]; int i, j; memset(&old_addr, 0xFF, sizeof(old_addr)); for (i = 0; i < n_lan_addr; i++) { memcpy(&old_addr[i], &lan_addr[i].addr, sizeof(struct in_addr)); close(lan_addr[i].snotify); } n_lan_addr = 0; i = 0; do { getifaddr(runtime_vars.ifaces[i]); i++; } while (i < MAX_LAN_ADDR && runtime_vars.ifaces[i]); for (i = 0; i < n_lan_addr; i++) { for (j = 0; j < MAX_LAN_ADDR; j++) { if (memcmp(&lan_addr[i].addr, &old_addr[j], sizeof(struct in_addr)) == 0) break; } /* Send out startup notifies if it's a new interface, or on SIGHUP */ if (force_notify || j == MAX_LAN_ADDR) { DPRINTF(E_INFO, L_GENERAL, "Enabling interface %s/%s\n", lan_addr[i].str, inet_ntoa(lan_addr[i].mask)); SendSSDPGoodbyes(lan_addr[i].snotify); SendSSDPNotifies(lan_addr[i].snotify, lan_addr[i].str, runtime_vars.port, runtime_vars.notify_interval); } } } int OpenAndConfMonitorSocket(void) { #ifdef HAVE_NETLINK struct sockaddr_nl addr; int s; int ret; s = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (s < 0) { perror("couldn't open NETLINK_ROUTE socket"); return -1; } memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_groups = RTMGRP_IPV4_IFADDR; ret = bind(s, (struct sockaddr*)&addr, sizeof(addr)); if (ret < 0) { perror("couldn't bind"); close(s); return -1; } return s; #else return -1; #endif } void ProcessMonitorEvent(int s) { #ifdef HAVE_NETLINK int len; char buf[4096]; struct nlmsghdr *nlh; int changed = 0; nlh = (struct nlmsghdr*)buf; len = recv(s, nlh, sizeof(buf), 0); if (len <= 0) return; while ((NLMSG_OK(nlh, len)) && (nlh->nlmsg_type != NLMSG_DONE)) { if (nlh->nlmsg_type == RTM_NEWADDR || nlh->nlmsg_type == RTM_DELADDR) { changed = 1; } nlh = NLMSG_NEXT(nlh, len); } if (changed) reload_ifaces(0); #endif } minidlna-1.1.5+dfsg/getifaddr.h000066400000000000000000000040261261774340000163400ustar00rootroot00000000000000/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * * Copyright (c) 2006, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __GETIFADDR_H__ #define __GETIFADDR_H__ #include #define MACADDR_IS_ZERO(x) \ ((x[0] == 0x00) && \ (x[1] == 0x00) && \ (x[2] == 0x00) && \ (x[3] == 0x00) && \ (x[4] == 0x00) && \ (x[5] == 0x00)) int getsyshwaddr(char *buf, int len); int get_remote_mac(struct in_addr ip_addr, unsigned char *mac); void reload_ifaces(int notify); int OpenAndConfMonitorSocket(); void ProcessMonitorEvent(int s); #endif minidlna-1.1.5+dfsg/icons.c000066400000000000000000001721441261774340000155240ustar00rootroot00000000000000/* Debian Open Use Logo * * The Debian logo can be obtained in various formats from * * http://www.debian.org/logos/ * * and is licensed under the following terms: * * Copyright (c) 1999 Software in the Public Interest * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /* Small Debian PNG logo (without "Debian") */ unsigned char png_sm[] = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x30\x00\x00\x00\x30" "\x08\x06\x00\x00\x00\x57\x02\xf9\x87\x00\x00\x00\x06\x62\x4b\x47\x44\x00\x00\x00\x00\x00\x00\xf9" "\x43\xbb\x7f\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x06\x20\x00\x00\x06\x20\x01\x1f\x10\x7e\xb8" "\x00\x00\x00\x09\x76\x70\x41\x67\x00\x00\x00\x30\x00\x00\x00\x30\x00\xce\xee\x8c\x57\x00\x00\x07" "\x75\x49\x44\x41\x54\x68\xde\xd5\x99\x6d\x70\x54\xe5\x15\xc7\x7f\xe7\xb9\xbb\x7b\x37\x41\x02\x2a" "\xd6\x02\x93\xd6\x16\x48\xb2\x49\x31\xb5\x88\xa5\xa5\x38\xb4\x76\x06\x18\x0d\x64\x13\xb7\xd5\x41" "\x3a\xb5\xd3\xd1\x8e\x75\xaa\x65\xca\xb4\x75\x68\x8d\xb5\xce\xb4\x7c\x51\xeb\x74\xea\xb4\x03\x82" "\x53\x3f\x40\x48\x36\x48\x29\x2d\x30\xa2\x1f\xaa\xad\x2f\x14\x51\xb2\x89\xe0\x14\x07\x19\x40\x30" "\x14\xf2\xb2\xef\xf7\xf4\xc3\x6e\x42\x4c\xee\x26\x7b\xc3\x12\xa6\xe7\xcb\xee\xf3\x72\xce\xf9\xff" "\xcf\x79\xee\xf3\x9c\xfb\x5c\xa1\xc4\xd2\x3d\xb5\x61\x86\xa6\xad\xe5\x8a\x94\x01\xef\x25\x12\xfa" "\xf6\x14\xe4\xfa\x32\x7c\x67\x2a\x69\xed\x79\x93\x86\xf2\x9b\xd9\x39\x50\x2a\x7f\x52\x2a\x43\xb1" "\xc0\xaa\xf9\x22\xd6\x43\x8a\xae\x06\x82\xc3\x86\x1c\xe0\x5d\xc0\xa0\x72\x01\xd1\x2a\x84\xfd\x46" "\xf5\xb9\xaa\xa4\x7f\x8f\xd0\x9a\xbd\xa2\x04\xba\xcb\x22\xb3\x1d\x27\xfb\x34\x68\xf3\x04\xd4\x4f" "\x20\xf2\xbc\xa0\x47\x55\xc5\xef\xf8\xac\x1d\x75\xfd\xad\xa7\x26\x85\x80\x12\xb1\xba\xed\xf4\x83" "\x8a\x3c\x0e\x4c\xbd\xd4\x40\xe4\xc1\x1c\x54\xd5\x0d\x35\x29\xff\xb6\x62\x33\x33\x21\x02\xb1\x40" "\xe3\xcf\x11\x59\x0d\xd4\x8d\x33\x35\x0e\xf4\x20\x7a\x12\x95\x24\x30\x0d\xa8\x05\xcc\xd8\xa8\xf4" "\x4d\xe3\x58\x6b\xaa\x53\x6d\x5d\x25\x27\xd0\x69\x87\x37\x08\xac\x1b\x63\x4a\xb7\xc0\x46\x55\xe7" "\x6f\xfe\x80\x39\x3d\xb7\x2f\xfa\xd1\xf0\xc1\x23\xac\xa8\xc8\xd8\xf6\x22\x44\x16\xa3\xfa\x55\x90" "\x25\x80\x3d\xca\x8a\xf2\x31\xaa\xcb\x42\xe9\x8e\xb7\x4a\x46\x20\x16\x0c\x3f\x81\xf2\x48\x81\xe1" "\x04\xca\x63\x35\xa9\xe8\x6f\x05\xb4\x58\x9b\xdd\x81\xe6\x1a\x07\xdd\x8c\xe8\x97\x5d\x86\x2f\x60" "\x58\x1e\x8a\x47\x5f\xbb\x64\x02\x31\x7b\x55\x03\x98\x1d\xa3\x74\x84\x73\xea\xf0\x40\x28\x55\xbf" "\x4d\x68\x71\xbc\x04\x64\x50\x94\x88\xd5\x15\xcc\xfe\x02\xd5\x47\x5d\x86\x4f\xfb\xfd\xdc\x38\x32" "\x93\x9e\x08\x74\xfb\xc3\xf5\x6a\x78\x59\x61\xfa\x88\xa1\x84\xe3\xe8\x57\xea\xd2\x1d\x07\x27\x02" "\x7c\x54\x90\x02\x8d\x8f\x22\xd2\x32\x9a\x20\xbb\x43\xc9\xe8\xed\x6e\x99\x35\xc5\x18\x76\x2c\x36" "\x8e\x00\x3f\x00\xf2\xa3\x74\x32\x35\xab\x54\xe0\x01\x42\xa9\x8e\xc7\x14\x9e\x1e\xd9\x2f\xb0\x22" "\x16\x6c\xba\xc7\x4d\xa7\x20\x81\xae\x60\xd3\xd7\x73\xbf\xe1\x6f\xa0\x2c\x18\x66\xee\x28\xea\x2c" "\x0a\x25\xdb\x9f\xb9\x91\x5d\xe7\x4a\x05\x7e\x88\x44\xb2\x7e\x2d\x70\x68\x14\x09\x75\x7e\xec\x89" "\x80\x88\xf5\xde\x3b\x65\xab\x2a\x55\x2f\x46\x44\xe0\xb8\x5a\xdc\x1a\x4a\xed\x78\xa7\xd4\xc0\x2f" "\xfa\x68\x71\x14\x7e\xf2\xc9\x3e\xe9\x00\x99\x76\xf4\xaa\xf0\xa7\x8a\x26\x50\x1d\x6f\x3d\xe1\x73" "\xcc\x53\xc0\x17\xf2\x5d\xbd\xa8\xb9\xa3\x76\xa0\xfd\xe4\xe5\x02\x3f\x28\xb5\xc9\xe8\x5e\x85\xdd" "\x83\x6d\x47\x75\x2b\xc2\x6f\xe6\xf4\xd5\x9f\x1d\x39\xd7\x57\xc8\x48\xf7\xd4\x86\x19\x4e\x8a\x86" "\x7c\x33\xab\x98\xbb\x42\xa9\xb6\x43\x78\x94\xc3\x44\x02\xc6\x4e\x2f\x05\x59\x09\x84\x44\xf5\x84" "\x63\xcc\xde\xa9\x09\x6b\x7b\x25\xad\xf1\x82\x8a\x2a\x5b\x10\x5d\x01\x20\xc2\x16\x47\xb3\x75\x47" "\xcb\x0e\xcd\x22\xce\x87\xe3\x66\x20\x66\x87\x3f\xab\x29\x6b\x35\xe0\xcf\x19\xe3\x8d\xda\x64\xdb" "\x5f\xbd\x82\xef\x0c\x36\x7d\xdf\xd8\x99\x33\x82\xac\x57\x78\x1f\xe1\x57\x6a\xe9\xb3\x38\xf4\xf5" "\x07\x32\x6b\x63\xc1\xc6\x1b\x0a\xe9\x66\xad\xec\xab\xc3\x9a\x7e\x4b\x7d\x5f\xf4\xc5\x2b\x8a\xce" "\xc0\x19\x45\xee\x1d\x6c\x08\xba\xcb\x2b\xf8\xae\x40\x78\xb5\xaa\xae\x37\xe8\x5d\xd5\xc9\x8e\xdd" "\x2e\x53\xa2\x87\x89\x5c\x53\xa8\xbc\x9e\x1f\xdf\x71\x3c\x66\x87\x4f\x00\xb3\x73\x09\x71\x2e\xdc" "\x40\x7f\x7a\xe4\x3c\xd7\x0c\x38\x8e\xa9\x06\xea\xf3\xcd\x01\xb5\xe5\x0f\xde\x22\xdf\xfc\x4d\x15" "\x7e\xed\xf3\xfb\x16\x16\x00\x0f\x40\x1d\xad\x3d\x63\xbd\x1b\x28\xec\x1c\x8a\xa1\xc8\x62\xb7\x02" "\xcf\x95\x80\x31\xd9\xe1\xd1\xdf\x1c\xea\x8d\x7e\x5c\x2c\xf8\x23\xac\xa8\x10\x75\xfe\xa4\x46\xee" "\x9e\xd7\xd7\x7a\xc6\x0b\x71\x17\x49\x0e\x91\x51\x71\xdd\xb2\x5d\x09\x28\x32\x77\xe8\xbf\xc3\x26" "\x2f\x1e\xd3\x76\x70\x3d\xf0\x6e\x6d\xbc\xfd\x9f\x97\x08\xfe\x93\x22\x7a\x77\xd1\x04\x50\x86\x22" "\x6e\x82\xd9\x0f\x8a\xf5\x71\x9c\x48\x99\xc0\xfd\xaa\xe2\x89\x74\xf7\xd4\x86\x19\x9d\xe5\x4d\x33" "\x47\x61\x16\xbd\x6e\x08\x87\xea\xab\x6e\xba\xae\x04\x44\x64\x70\x5d\xa6\xab\x7a\x77\x16\xbd\x7c" "\x7a\xcb\xb3\xd3\x81\x0a\x1f\x3a\x6e\x1d\x3f\x5c\xaa\x7a\x17\xf4\x0c\x0c\xa4\xcf\x8f\xc2\xa1\x52" "\x99\xff\x9b\x72\xc4\x1c\x70\xd3\x75\xdd\x85\x04\x35\x0a\x08\x9c\xf2\x52\x1a\xfb\x15\x2b\x03\xa4" "\x55\xed\x62\x75\x72\xfe\x5a\x1c\x60\xd4\xc3\xac\xc2\xa9\xbc\xf7\xe7\xa6\x27\x52\x6d\x6e\xba\xee" "\xbb\x90\xe6\xfa\x1d\xc1\xd3\xa9\x9b\xc2\x32\x00\x62\xcc\xd2\x62\x75\x74\xac\x8a\x58\x99\x93\xfb" "\x31\x2f\xf6\x93\x75\x7d\xc5\x74\x5f\x42\x79\xa3\xa2\x9c\xc7\x83\x94\xc7\xa7\x7c\x04\xf4\x09\x84" "\x8a\xd5\xe9\xb4\x57\xce\x71\xeb\xef\x2a\x6b\x5a\x0c\x7c\x09\x48\x0c\x24\x53\x2f\x9f\xa7\xd2\xf5" "\x5d\xc3\xfd\x21\x96\xa1\x74\x06\xbc\x10\xf8\x1c\x9b\x13\x22\xba\x0b\xd1\xc8\x71\x22\x65\xc5\xe8" "\x98\x80\x35\x6a\x7b\xfc\x37\x8d\xd3\xd5\xd1\x57\xf2\xcd\x3d\x0b\xd8\x19\x5f\xc0\x1f\x33\x45\x13" "\x10\xa1\x2f\xc7\x83\x69\x5e\x08\x00\xa8\x5a\x5b\x51\xae\xee\x0f\x64\xd6\xba\x8d\xc7\xec\xf0\x1d" "\xb1\x40\xf8\x97\xff\xe1\xbb\x41\x00\xb7\x33\xc6\x0e\x98\x65\x80\xa5\xca\x26\xe3\xe8\x13\x79\x2c" "\xae\xcf\xa2\xeb\xfa\xeb\x0a\x34\xae\x57\x91\xc7\x01\x45\xf4\xf3\xa1\x44\xc7\xb1\x62\xc0\x1f\x2e" "\x8b\x7c\x86\x38\x7d\x26\x90\x79\x12\x61\x8d\x88\x7e\xa7\x26\xd1\xf1\x67\x2f\x01\x88\xd9\x4d\xf7" "\x83\xfe\x0e\xc8\x58\xca\xcd\x55\xa9\x68\x6c\xac\xf9\xee\x07\x99\xf0\xdf\xa1\x64\x38\xb2\xb0\x58" "\xe7\x92\xc9\xce\xb0\x82\x4c\x4b\xa5\xfa\x1f\x40\xf4\x2d\x55\xd9\x14\xb3\x9b\x97\x15\xa3\x7b\x84" "\x15\x15\x9d\x81\xf0\x46\xd0\x67\x81\x73\xc6\x91\xa5\xe3\x81\x2f\x48\xc0\x38\xbc\x3e\x44\x06\xf5" "\x8d\x67\x64\x50\x6a\xd3\xed\x07\x2c\xc9\xa6\xcd\x94\xe0\x55\xc0\x3a\xd0\x97\x40\xdb\x3a\xed\xf0" "\x86\xc3\xf6\xca\xb9\x6e\x3a\xc7\x89\x94\x75\xda\x4d\x0f\x67\x82\xc1\x63\x22\x7c\x0f\xe5\x35\x44" "\x17\x55\xa7\xdb\xdf\x28\x2a\x68\x6e\x9d\xfb\x59\xea\xfb\xb4\x7d\xf5\x79\xa0\x1c\xe1\x85\x50\x22" "\x7a\x4f\x31\xc6\x86\x4b\x2c\xd0\xd8\xac\x22\x4b\x44\xa9\x40\xb8\x97\xdc\x1a\xde\x87\xe8\x76\x71" "\xa4\x1f\xe1\x5a\xd0\x65\x8a\x2c\x04\xae\x03\x4e\x23\xfc\xb4\x26\x11\x7d\xde\xcb\xd9\x53\x70\x0f" "\xee\xb2\xc3\xfb\x14\x6e\x43\xe8\xe9\x4f\x64\x2a\x27\x72\xa3\x1c\xb3\xc3\x55\xa0\xf7\x81\xcc\x57" "\x78\x5b\x94\x25\x08\x21\xc0\x02\x2c\x81\xb3\xaa\xec\x53\xd8\x63\x52\xd9\x5d\x35\xbc\xd8\xeb\xd5" "\x47\x41\x02\xb1\x40\xd3\x23\x48\x6e\x07\x00\xb6\xfb\xd0\x9f\xcd\x4b\x76\xbc\xef\xd5\x01\xe4\xca" "\x6b\x4b\xb3\x7e\xc5\xf8\xc5\x68\x4f\xd6\x31\xa7\xad\x40\xea\x5c\x75\xef\xce\xb3\x13\xb1\x57\x5c" "\x06\xca\x57\xce\xd2\xac\xf5\x01\x17\xcb\x8d\x13\xfd\xc9\x4c\xd5\xa5\xde\xed\x77\x97\x45\x66\x6b" "\x3c\x79\x61\x22\xd1\xf6\x44\x00\x20\x16\x0c\x6f\x43\x89\xe4\x9b\x69\x11\x96\xd7\x24\xa2\x2f\x95" "\xc2\x71\xa9\x64\xbc\x8b\xad\xdf\x0f\xfb\xef\x57\x87\xaf\x5d\x69\xc0\x9e\x08\x84\x12\xd1\x57\x50" "\x2e\x5e\xac\x0a\xf7\x69\x09\xbf\xea\x5c\x76\x02\x39\xcc\xe6\x07\xc0\x60\x1d\x72\x7d\x97\x1d\xbe" "\xfd\x4a\x83\xf6\x44\xa0\x26\x77\x17\xf4\x54\xbe\xe9\x43\xb9\xf3\x4a\x83\xf6\x44\x00\x20\x95\xec" "\x6f\x01\x8e\x01\x20\xac\xe9\x0c\x36\xdd\x7a\xa5\x81\x7b\x22\x50\xcf\x9e\x7e\x55\x1a\x80\x0b\x80" "\x11\xd5\x17\x0a\x95\x06\x93\x2d\xde\xbe\xd0\xd8\xcd\xcb\xc0\xf9\x0b\xb9\xb3\x61\x40\x2d\x99\x3b" "\x19\x77\xa5\x63\x49\x51\x19\x18\x94\x50\xb2\xed\xef\x88\xfe\x90\x5c\xad\x52\x6e\xb2\xce\x7a\xa5" "\xc5\x93\x8d\x52\xcb\x84\xb6\xc4\xce\x40\xd3\xb7\x45\x74\x0b\xb9\x8f\x73\x27\x7d\x46\x6e\x99\x17" "\x6f\xff\x70\x22\xb6\x2e\x55\x26\x14\xbd\xda\x54\xfb\x56\x31\x72\x1b\x70\x16\x98\x99\xc9\xea\xc1" "\xfc\x3b\xec\xff\x07\x01\x80\x9a\x78\xfb\x3f\x44\x32\xb7\x00\x7b\x11\xae\x75\x1c\x5d\xd7\xed\x0f" "\xd7\x4f\xf6\x92\x2a\xc9\xa9\xda\x15\x68\xfc\x96\x8a\x3c\x09\xcc\x44\xe5\x75\xc7\xca\x3e\x54\x17" "\xdf\xf1\xaf\xc9\x20\x50\x92\x68\xd5\xa4\x3a\xb6\x49\x32\x5b\x23\xf0\xb0\x88\xda\xc6\x31\xfb\x63" "\x76\xe3\x83\x93\x51\x76\x5c\x16\x07\x87\xfd\xcd\x37\x19\xcb\xb9\x53\x95\x6b\x44\xe8\x11\x34\x96" "\x75\xf4\x40\x6d\xea\xa6\xae\x89\x7e\x4b\x2e\x24\xff\x03\xb6\xd7\xe8\x5b\x9c\xd6\x0f\x61\x00\x00" "\x00\x25\x74\x45\x58\x74\x64\x61\x74\x65\x3a\x63\x72\x65\x61\x74\x65\x00\x32\x30\x31\x31\x2d\x30" "\x36\x2d\x31\x38\x54\x31\x31\x3a\x35\x33\x3a\x31\x32\x2b\x30\x32\x3a\x30\x30\x2f\xfd\x92\x9e\x00" "\x00\x00\x25\x74\x45\x58\x74\x64\x61\x74\x65\x3a\x6d\x6f\x64\x69\x66\x79\x00\x32\x30\x31\x31\x2d" "\x30\x36\x2d\x31\x38\x54\x31\x31\x3a\x35\x33\x3a\x31\x32\x2b\x30\x32\x3a\x30\x30\x5e\xa0\x2a\x22" "\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b" "\x73\x63\x61\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60" "\x82"; /* Large Debian PNG logo (without "Debian") */ unsigned char png_lrg[] = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x78\x00\x00\x00\x78" "\x08\x06\x00\x00\x00\x39\x64\x36\xd2\x00\x00\x00\x06\x62\x4b\x47\x44\x00\x00\x00\x00\x00\x00\xf9" "\x43\xbb\x7f\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0f\x51\x00\x00\x0f\x51\x01\x73\x03\x96\x37" "\x00\x00\x00\x09\x76\x70\x41\x67\x00\x00\x00\x78\x00\x00\x00\x78\x00\x15\x45\xc1\x5b\x00\x00\x11" "\x9f\x49\x44\x41\x54\x78\xda\xed\x9d\x7b\x98\x54\xc5\x95\xc0\x7f\x3d\xcc\x50\x33\x0c\x18\xf0\xfd" "\xc2\x27\x44\x0a\x10\x13\x1f\xeb\x0b\xe3\x23\x51\x71\x05\xa5\x34\x90\xac\x66\x23\x1b\x37\x62\xe2" "\x33\x89\x6c\x34\x8a\x41\x57\x65\x35\xd1\x10\x1f\x71\x5d\x4d\x40\xb3\x31\x2a\x59\x4a\x89\xcf\x64" "\x13\x35\x51\x0c\xea\x2a\xa8\xa1\x20\x82\x46\x08\x0a\x51\x84\x98\x61\x66\x6a\xe8\x99\xde\x3f\xaa" "\x46\x9a\xa6\x1f\xd3\xf7\xde\xbe\xb7\x7b\x98\xdf\xf7\xf5\x37\xb7\xfb\xbe\xce\xad\x33\x55\xb7\xea" "\xd4\x39\xa7\x52\x6c\x23\x18\xa1\x06\x4a\xab\x5b\x00\x56\x31\xa9\xa9\x45\xa4\x87\x66\x52\x29\xdb" "\xbf\x3e\xd3\x96\x6e\xe9\x6c\x3b\x00\xd1\x9a\x62\x6e\x67\xd2\x72\x46\x4d\x7d\xd2\x02\x44\x89\x11" "\xea\x53\xc0\xbf\x00\xbf\x01\xf6\x04\x46\x01\x23\x81\x97\x80\x9b\x81\x16\x80\xa1\xcc\x6d\x33\xa9" "\x89\x1d\xa9\x4c\xdd\x88\x74\x57\xfa\xf5\x11\xcc\xff\x7b\xd2\xb2\x57\x8a\x54\xd2\x02\x84\xc5\x08" "\xd5\x0f\x38\x0d\xb8\x04\x38\x36\xcf\x21\x0b\x81\xb7\x80\xc1\xc0\x76\x40\x06\x58\x06\xfc\xb4\xd1" "\x66\x5e\x4b\x35\x34\xb7\xb7\xd5\xb5\x0e\x05\x76\x05\x56\x03\x2b\x81\xf9\xc0\xe3\x80\x96\x56\xff" "\x25\xe9\x67\x0c\x43\xcd\x2a\xd8\x08\x35\x18\x38\x17\xb8\x10\xd8\xa7\x8c\x53\xdb\x81\x25\x40\x1a" "\xf8\x04\x30\x0c\xe8\x97\xb5\xff\x6f\xc0\x00\xa0\x01\xf7\xcf\xf0\x22\x30\x0f\x98\x27\xad\x5e\x9e" "\xf4\x73\x97\x4b\xcd\x29\xd8\x08\x75\x00\x70\x31\x70\x0e\xd0\x1c\xf3\xed\x9f\x06\x7e\x00\x3c\x2a" "\xad\xce\x24\x5d\x16\x3d\xa1\x66\x14\x6c\x84\x1a\x05\x5c\x8f\x6b\x8e\x93\x96\xfb\x4d\xe0\x56\x60" "\xb6\xb4\x7a\x63\xd2\x65\x53\x8c\xa4\x0b\xaa\x24\x46\xa8\xa1\xc0\xb5\xc0\x97\x81\xba\xa4\xe5\xc9" "\x61\x03\x70\x37\xf0\x1a\x70\x30\x20\x81\x46\x60\x01\x30\x47\x5a\xfd\x66\xd2\x02\x56\xad\x82\x8d" "\x50\x43\x80\x2b\x81\x0b\x70\x85\x56\x6b\xbc\x20\xad\x3e\x2a\x69\x21\xaa\x52\xc1\x46\xa8\x63\x81" "\xfb\x81\xdd\x93\x96\x25\x24\x8f\x01\xdf\x04\xfa\x49\xab\x4d\x12\x02\x54\x95\x82\x8d\x50\x75\xc0" "\x55\xc0\xd5\x6c\xd9\xb3\xad\x65\xd6\x02\xf7\xe2\xca\x7a\xba\xb4\xda\xc6\x79\xf3\xaa\x50\xb0\x11" "\x6a\x1c\xb0\x1b\xf0\x25\xe0\x84\xa4\xe5\xa9\x20\x4b\x80\x29\xd2\xea\x97\xe2\xba\x61\xe2\x0a\x36" "\x42\x9d\x89\x6b\x8e\xfb\xc7\x7c\xeb\x34\x60\x81\x0e\xff\x37\x8d\x33\x76\x54\xda\xba\x97\x06\xfe" "\x03\xb8\x46\x5a\x9d\xae\xf4\x43\x26\xaa\x60\x23\xd4\x39\xc0\x8f\x89\xbe\x39\x4e\xe3\xcc\x95\xaf" "\x02\xaf\xe3\x14\x38\xc8\xff\xfd\x0d\xf0\x81\xb4\xba\x2b\x8f\x3c\xfd\x80\xbd\x71\xc6\x8f\xfd\xfd" "\x67\x18\x70\x34\xb0\x63\xc4\x32\x6a\xe0\x8b\xd2\xea\x8e\x88\xaf\xbb\x05\x89\x29\xd8\x08\x75\x01" "\x70\x5b\xc4\x32\xb4\x01\xf7\x00\xb7\x48\xab\xff\xec\xef\xf3\xf1\x24\x43\x08\x59\xeb\x81\xe3\x81" "\x49\x80\x22\x3a\x65\x3f\x06\x9c\x59\xc9\xf7\x72\x22\x0a\x36\x42\x5d\x01\xdc\x10\xf1\x65\xe7\x02" "\x97\x49\xab\x57\x56\x58\xf6\x6e\x65\x7f\x01\xf8\x67\xc2\xbf\x5a\x7e\x0d\x9c\x2e\xad\x6e\xab\x84" "\xbc\xb1\x2b\xd8\x08\x75\x1d\x6e\x7c\x1b\x05\xab\x81\x9f\x01\xf7\x49\xab\xff\x98\xc0\xb3\x0c\x03" "\xbe\x0f\x9c\x1e\xf2\x52\xcf\x02\xe3\xc3\xb6\x34\xf9\x88\x55\xc1\x46\xa8\x2b\x81\xeb\x22\xba\xdc" "\x63\xc0\x33\x38\x83\xc2\xf3\x71\x3e\x47\x9e\xe7\xfa\x2c\x30\x1b\x18\x1a\xe2\x32\x0b\x80\x53\xa4" "\xd5\x1f\x45\x29\x5b\x6c\x0a\x36\x42\x5d\x02\xcc\x0a\x79\x99\x45\xc0\xef\x80\xa5\x80\x01\x96\x4a" "\xab\xd7\xc4\xf5\x0c\x25\x9e\x6f\x47\xe0\xbf\x81\x93\x43\x5c\xe6\x71\x69\xf5\xa9\x51\xca\x15\x8b" "\x82\x7d\x6f\x79\x76\x88\xfb\x3d\x8f\x1b\x56\xfc\x3a\x0e\x79\x43\x3c\x67\x1d\x30\x1d\x98\x11\xe2" "\x32\x17\x4b\xab\x6f\x8b\x4a\xa6\x8a\x2b\xd8\x08\xb5\x03\xb0\x02\x37\xf7\x1a\x84\x7b\x80\xf3\xa5" "\xd5\x35\xe3\x4e\x13\xb2\x13\xd9\x0e\x1c\x26\xad\x7e\x23\x0a\x59\xe2\x98\x9d\xf9\x0e\xc1\x95\x7b" "\x0d\x30\xb5\x96\x94\x0b\x20\xad\x9e\x89\x1b\x02\x06\xa1\x11\xf8\xb9\x11\x2a\x92\x09\x96\x8a\x2a" "\xd8\x08\x75\x32\x6e\x36\x28\x08\xef\xe0\xdc\x6c\x6a\x95\x4b\x81\x87\x02\x9e\x3b\x1a\xb8\x29\x0a" "\x21\x2a\xd6\x44\x1b\xa1\xf6\xc4\xf9\x42\x35\x94\x79\xea\x1b\xc0\x0f\x71\x73\xac\x6f\x48\xab\x5b" "\x2b\x25\x63\xa5\x31\x42\xf5\x07\x9e\x20\xb8\x7d\xfd\x54\x69\xf5\xe3\x61\x64\xa8\xa4\x82\x6f\x02" "\xa6\x95\x71\xca\x3a\x9c\x7f\xd5\x83\xb5\xe2\x0e\xd3\xc3\x72\x18\x84\x1b\xe7\x7e\x3a\xc0\xe9\x7f" "\x01\xf6\x0d\x63\xb3\xae\x88\x82\xfd\x43\xad\xa2\xe7\xef\xde\xd7\x81\x89\xd2\xea\xb7\x2a\x21\x4f" "\xd2\x18\xa1\x76\xc1\x8d\x73\xf7\x0b\x70\xfa\x64\x69\xf5\xdc\xa0\xf7\x0e\xf5\x0e\x5e\xcc\x49\x85" "\x9c\xde\xbe\x4a\xcf\x95\x3b\x0f\x38\xaa\xb7\x2a\x17\x40\x5a\xbd\x16\xf8\x27\xa0\x2b\xc0\xe9\x17" "\x86\xb9\x77\x28\x05\xf7\x17\xcd\x6f\x2e\x19\x70\xc6\x6e\xd9\xbf\x79\x5b\xed\x25\x3d\x38\x3d\x83" "\x1b\x2f\x7e\xbe\x12\x26\xba\x6a\x43\x5a\xfd\x22\xc1\x7a\xd6\x9f\x31\x42\x1d\x18\xf4\xbe\x81\x15" "\xfc\x26\xa7\x6c\x47\x26\xf5\xf6\xc8\xd6\x79\xef\xe5\xec\x9a\x0c\xec\x55\xe2\xf4\x34\xae\xe9\xb9" "\xa6\x37\xbd\x6f\x7b\xc0\x55\x38\xc7\xfa\x72\x09\x5c\x8b\x03\x2b\xb8\xbd\xa9\xff\x27\x3a\x3a\x5a" "\x4e\xca\xfe\xcd\x9b\xeb\x66\x96\x38\x35\x83\xf3\x6a\xf8\x45\x88\x82\xaa\x49\x7c\x4b\x75\x51\x0f" "\x0f\xff\x00\xf8\xbd\xdf\x3e\xc6\x3b\xfa\x97\x4d\x60\x05\x1f\xd8\xf6\xc8\xaa\x83\xf8\xd5\xc7\x3e" "\xc1\x46\xa8\x29\xc0\xaf\x28\x5d\x7b\x2f\x92\x56\xff\x2c\xa2\x32\xab\x39\xa4\xd5\xf3\x81\x57\x4a" "\x1c\x36\x06\x18\x01\x7c\x1d\xe8\x04\x9e\xc2\x39\x2c\x94\x4d\x24\x86\x0e\xdf\x4b\xfc\x2f\x4a\x0f" "\x05\xae\x92\x56\xdf\x11\x65\x81\xd5\x28\xa5\x5a\xb9\x4e\x69\xf5\x3a\x6f\xae\xfc\x09\xb0\x97\xb4" "\x7a\x55\x90\x1b\x45\x65\xc9\xfa\x2a\xa5\x0d\x1a\x37\x4b\xab\xaf\x8f\xb6\x9c\x6a\x96\x79\xb8\x00" "\xb8\x42\x1c\x92\xb5\x7d\x35\x10\x78\x0a\x31\xb4\x82\x7d\xaf\xf9\xfc\x12\x87\x3d\x2c\xad\xbe\x2c" "\xca\x12\xaa\x65\xbc\x3f\xd8\xac\x22\x87\x5c\x6d\x84\xba\xcf\x08\xd5\xcf\x4f\x87\x2e\x0c\x7a\xaf" "\x28\x3c\x08\x4f\x07\xf6\x28\xb2\x7f\x25\xf0\x95\x4a\x14\x54\x50\xfc\xb4\xde\xd1\xc0\x81\xb8\x70" "\x13\x89\x73\xb0\xfb\x08\x78\x0f\x58\xe3\xff\x3e\x29\xad\x7e\xb6\x42\x62\x68\xe0\x0e\xf2\x57\xb2" "\x61\xfe\xf3\x14\xce\x63\xe5\x0d\x23\xd4\x20\x69\x75\xd9\x71\xcc\x51\x28\xb8\xd8\x64\x42\x27\x70" "\x96\xb4\x7a\x7d\x85\x0a\xa9\x2c\xfc\x0c\xcd\x97\x81\x6f\x01\x9f\x2c\x70\xd8\x98\xac\xed\xcb\x8d" "\x50\xcb\x71\x73\xd9\x73\xa4\xd5\xef\x46\x25\x8b\xb4\x7a\xad\x11\x6a\x01\x30\xb6\xc8\x61\x63\x71" "\x0a\x7e\x1e\xf7\x4f\xb8\xa4\xdc\xfb\x94\xad\x60\x23\xd4\x27\xa5\xd5\x7f\xf2\xdb\x23\x71\x0e\x68" "\x85\x98\x99\xb4\x3b\x8d\x97\x33\x85\x9b\xdd\xf9\x36\xb0\x4b\x9e\x43\xd6\xe1\xdc\x69\x37\xe2\xac" "\x4d\x19\xff\xb7\xd1\x3f\xdf\xf5\xc0\x74\x3f\xcf\xfb\xc3\x08\xc7\xee\x9a\xe2\x0a\x3e\x08\x40\x5a" "\x9d\x31\x42\xfd\x2d\xc8\x0d\x82\xd4\xe0\x23\x80\x3f\xf9\xed\xaf\x97\x38\xf6\x1b\x46\xa8\x5b\x92" "\xac\xc1\x46\xa8\x66\x5c\xe8\xc8\x99\x39\xbb\xfe\x0c\x3c\xec\x3f\xcf\x15\x9b\x73\x36\x42\x8d\x01" "\x4e\x05\x3e\x0f\x9c\x62\x84\xfa\x8a\xb4\x7a\x75\x04\xe2\x3d\x85\x4b\x2d\x51\x88\x31\x46\xa8\x94" "\xff\x87\x0a\xe4\x3f\x1d\x44\xc1\xd6\x3f\xf4\x20\x5c\x73\x57\x8c\xbb\x13\x56\xee\x3e\xc0\x23\x6c" "\xd9\xec\x2e\x01\xce\x2b\xa7\x65\x91\x56\xbf\x86\x9b\xbe\x9c\x69\x84\x3a\x18\xf8\xa9\x11\xea\x52" "\xff\x7b\x18\x56\xe0\x5a\x8b\x7c\x93\x3e\x6b\x70\xb9\x45\x86\xe1\xe2\x91\xd7\x19\xa1\xb6\x2b\xd7" "\x29\x2f\x48\x2f\xfa\x39\xff\xf7\x34\x8a\x0f\xbe\xdb\x89\x68\xd2\x3a\x08\x46\xa8\x81\xc0\x6f\xd9" "\xac\xdc\x4e\xe0\x46\xe0\xe0\x30\xaf\x0d\x69\xf5\x2b\xc0\x38\xe0\x38\xef\x36\x1b\x18\x69\x75\x3b" "\xae\x33\x97\x8f\x5d\x71\x2e\xb9\xcd\xfe\xd8\x2e\xff\x0c\x65\x51\xb6\x82\xb3\x9a\xa6\x33\x4a\x1c" "\x7a\x8f\xb4\xfa\xbd\x52\xd7\xab\x20\xb3\x80\x7d\xfd\xf6\x32\x60\xac\xb4\xfa\xf2\x28\xa2\x08\xa4" "\xd5\x1d\xd2\xea\x5b\x81\x8d\xfe\xfd\x1e\x86\x62\xb3\x68\x9f\x95\x56\x2f\xca\xfa\x5e\x76\x33\x1d" "\x48\x38\x23\x54\x13\xce\x56\x3a\xa0\xc0\x21\x1d\xc0\xfe\x49\x65\xa8\x31\x42\x4d\xc0\x65\xca\x01" "\xf8\x3f\xe0\x98\x4a\x45\x0e\x44\x20\xeb\xf9\xc0\x9d\x05\x76\xaf\x04\x46\x07\x19\x1e\x75\x13\xd4" "\xd0\x71\x32\x85\x95\x0b\x2e\x77\x45\x52\xca\x3d\x10\xb8\xcf\x7f\xfd\x10\x17\xfb\x53\x95\xca\xf5" "\x14\x8b\xc8\xd8\x0b\x38\xa9\xa7\x17\xca\x47\x50\x05\x97\x6a\x9e\xef\xaa\x64\x89\x14\xc2\x08\xb5" "\x2f\xae\x67\x3a\x18\x37\xcc\x39\x5b\x5a\xfd\x4e\x12\xb2\x94\x41\x31\x27\x00\x0b\xfc\x32\xcc\xc5" "\xcb\x56\xb0\x11\xaa\x01\x98\x50\xe4\x90\x45\xd2\xea\x57\xe3\x29\x9b\xad\x98\x85\x0b\x24\x07\xb8" "\x56\x5a\xfd\x64\x42\x72\x94\x43\xb1\x8e\x53\x1d\x6e\xee\x3c\x30\x41\x6a\xf0\xf1\x14\x77\x67\x9d" "\x5d\xf9\x32\xd9\x1a\x23\xd4\x08\x36\xff\xe3\xbd\x02\xfc\x7b\x12\x72\x04\xa0\x58\x0d\x6e\xc0\xa5" "\x64\x0c\x4c\x10\x05\x17\x3b\xa7\x03\x67\x5a\x4b\x82\x4b\xd8\xdc\x69\xbc\x27\x5f\x80\x77\x95\x52" "\x6a\xe8\x73\xa5\x11\x2a\x88\x47\x26\x10\x4c\xc1\x2f\x17\xd9\xb7\x4c\x5a\xbd\x2e\x96\x62\xc9\xc2" "\xbf\x36\x26\xfb\xaf\x16\xf8\x79\xdc\x32\x84\xa0\xd4\x3f\xe2\x79\x04\x18\xff\x76\x13\x64\x1c\xfc" "\x41\x91\xdd\x49\x45\xfa\x9d\x08\x6c\xef\xb7\x1f\x91\x56\x6f\x48\x48\x8e\x20\x94\xb2\x26\xfe\x31" "\x8c\xc5\x2c\xea\xd0\x95\xd8\x0d\x1b\x7e\xea\xef\x9a\xac\x9f\xe6\xc4\x2d\xc3\x66\x59\xce\x98\x1a" "\xe0\xb4\xed\x4a\xec\xbf\x3f\x8c\x4c\x51\x2b\x38\x89\x1a\xfc\x39\xe0\x50\xbf\xdd\x86\xf3\x0b\x8b" "\x9d\xb7\x99\xd2\x08\x04\x71\x6a\x28\xa5\xe0\x50\x21\xb3\x35\x5f\x83\xd9\x3c\x2c\x02\x78\x3f\xa9" "\x48\xc4\x7d\x99\xd3\x0e\x99\x3b\x03\x9c\x3a\xa4\xc0\xef\x69\x60\x3d\x2e\xea\x23\x30\x41\xc6\xc1" "\xc5\xcc\x9b\x49\xd4\xe0\xec\x8c\x37\xb1\x77\xf0\xb2\x91\x56\xdf\x12\xe0\xb4\x83\x0a\xfc\x9e\x01" "\xee\xf5\x13\x12\x81\x09\x52\x83\x8b\x29\x38\x89\x1a\xbc\x43\xd6\xf6\x07\x81\xaf\x92\x1c\x85\x9c" "\x07\x16\x03\xf3\xfc\xac\x58\x60\xa2\x56\x70\x12\x35\x38\x5b\xc1\xeb\xfc\x90\xa9\x26\xf0\x1d\xc4" "\x15\x05\x76\x5f\x28\xad\xfe\x3d\xc1\xe2\x99\x3e\xa6\x37\xbc\x83\x37\x65\x6d\x0f\xc7\x39\xd3\xd5" "\x04\xde\x18\x93\x2f\xcf\x56\x17\x7e\x6a\x70\x84\xd5\x6d\xaf\x32\x71\x70\xd0\x7b\x44\x59\x83\x5b" "\xa3\x4e\x01\xd4\x43\x36\x64\x6d\x1f\xc2\xe6\x39\xe0\x58\x79\x99\xf3\x82\xb6\x1c\xf9\x9c\x06\xde" "\xc3\x3b\x53\x2c\x6b\x9c\xb0\xcf\x06\x36\x04\x0e\xce\x0b\xe2\xb2\x53\x48\xc1\x49\x19\x39\x36\xe4" "\x7c\xdf\x3b\xce\x9b\xbf\xcd\x94\xc6\xd6\xa6\x8d\x3b\xf7\x6f\x5b\xb5\xb6\xdc\x73\x7d\x13\x9d\x2f" "\x6d\xd2\x02\xe0\x5d\x80\xae\x4c\xff\xfa\xe3\x79\x26\xf0\x84\x43\x94\x35\x38\x29\x05\xe7\xfa\x7c" "\xed\x13\xd7\x8d\x97\x35\x4d\xda\xa3\x95\x8d\xf5\xa9\xae\xae\xd1\xc3\x79\x22\x88\xa7\xc8\x09\xe4" "\x4f\x9e\xf6\x23\x5c\x00\x7d\x68\xa2\xac\xc1\x49\x2d\x4e\xb1\x38\xe7\x7b\x90\x28\xfa\xb2\xc9\x30" "\xa9\xdf\xd2\xae\xae\xd1\x5d\xcd\x9b\x16\xd5\x77\xd6\x05\xcd\x23\x32\x25\xcf\x6f\x4b\xa4\xd5\xcf" "\x44\x25\x67\x94\xb3\x49\x71\xe7\x7b\xee\x66\x11\x7e\x45\x33\xcf\x91\x46\xa8\xdd\x02\x5e\xab\xc7" "\x98\x01\x9d\x3b\xa7\xe8\x3a\xa0\x5f\x67\xfd\x85\xb2\xfd\xe1\x67\xca\x3a\x57\xa8\x3a\x23\xd4\x59" "\xe4\x77\x9c\x98\xd5\xbd\xf1\x2a\x13\x07\x37\x34\xd4\x6d\x08\x23\x67\x94\x4d\x74\x22\xc3\x13\x9f" "\xa0\xe4\x0f\x59\x3f\xd5\xe3\x96\xb7\xab\x28\x23\x5b\xe7\xbd\x97\xa9\xe3\xa5\x4c\x26\x7d\x6f\x80" "\xd3\xe7\xe3\xa6\x55\x9b\x72\x7e\x7f\x17\xe7\xc3\x0d\xc0\x80\xa6\x86\xe6\xe1\x2d\x73\xdf\x0f\x23" "\x67\x94\x35\x38\xc9\xf1\xe7\xa3\x39\xdf\xff\x35\x02\x6f\xc7\x92\xc8\x36\xfd\xc2\x28\x3b\xbf\xac" "\xd5\xd0\xbc\x3f\x79\x21\x3f\xab\x1f\x64\x27\x08\x4f\xb7\x11\xda\x97\x2c\xca\x1a\x9c\x54\x13\x0d" "\xce\x8b\x24\xdb\xf3\x70\x5f\xdc\x14\x62\x35\x32\x96\xad\x2b\xc3\x52\x9c\x45\xeb\xb7\xdd\x3f\xbc" "\xcd\x94\xc6\x36\x86\x84\x5e\x34\x33\x48\x27\xab\xda\xde\xc1\xac\xb1\xeb\x5b\x77\x15\x43\x7e\x8c" "\x8b\x3f\xea\x66\x1a\x01\x66\x96\xde\x1c\x38\x69\xa7\xf4\xa6\xf4\x77\x49\xb1\x03\x19\x06\x01\x83" "\x20\xb3\xb8\xcb\x36\x5c\x36\x8a\xb9\x51\xa4\xdf\x1f\x9d\xf3\x3d\x8d\x0b\xab\x99\x42\x56\xcc\x70" "\x3d\x1b\x53\x87\x32\x67\x53\x19\xd7\xcd\x4b\x90\x1a\xdc\x42\x7e\xf3\xd9\xde\x7e\xcd\x83\xd8\x19" "\x3a\x70\xc8\xf6\x64\x52\xb7\xb1\x65\x2d\xfe\x9c\x11\xaa\xec\xe4\x25\xc3\x5b\xe6\xbe\x5f\xd7\x3f" "\x3d\x23\xd5\x95\xd1\x90\x5a\x0d\xa9\xa5\xf5\xd6\x4e\x8b\x42\xb9\x7e\x15\xb7\x73\x73\x7e\xbe\x4b" "\x5a\xbd\x04\xb8\x3c\x7b\x99\xbc\xe5\xbc\x1f\x5a\xb9\x10\xdc\xf1\x7d\x0d\xf9\xa3\xf4\x3e\x9d\xe3" "\x89\x1f\x1b\x4b\x9a\xce\x38\x22\xd5\x95\x19\xc3\x96\x2e\xbb\x6d\xc0\xa1\xbe\x00\x13\xc5\xfb\x6b" "\x3f\xc1\xd6\xb1\xd4\xc7\xe7\x0e\x8b\x32\x90\x4a\x15\x9e\x84\x28\x8b\xa0\xb6\xe8\x42\x56\x9b\x89" "\x15\x2c\xa3\xad\x30\x8d\x13\x8f\x7b\x99\x09\x03\x00\x52\x9d\x9d\x1b\x37\xda\x9d\x66\xb3\xe5\x04" "\x79\x13\x70\xbf\xcf\x19\x99\x18\x46\xa8\xe3\x70\x19\x73\x72\x95\xbb\x96\x2d\x6d\xe9\x00\x44\xa5" "\x5c\x08\xae\xe0\x42\x56\xab\x6f\x1a\xa1\x76\x8a\xbe\x88\xf2\x93\xca\xa4\x0e\x6b\x6e\xac\xff\x36" "\x80\xec\x78\xe4\xf5\x26\xd6\x0b\xdc\x42\x19\xd9\xf1\x3e\x07\x01\x77\x25\xf5\xfa\x30\x42\x7d\x01" "\xe7\x8c\x9f\x9b\xf9\x6f\x01\x2e\xb7\x49\xd8\x08\xc5\xa2\x44\xad\xe0\x41\x14\x8e\x9c\x8f\x9c\x16" "\x9b\xbe\x83\x0c\x13\x5f\x6f\x3a\x7d\xa8\x11\x6a\xe7\x51\xcc\x6d\xf1\x69\x03\xc7\xb1\xe5\xdc\xf0" "\x14\xe0\x61\x23\xd4\x80\x40\x37\x0a\x80\x11\xaa\xd1\x08\x35\x0b\xe7\xe1\x99\xdb\x82\xdc\x8f\xf3" "\x2f\x7f\x36\x4c\xdc\x51\x4f\x08\xaa\xe0\x62\xd9\xc8\x63\xb3\x49\x1f\xca\x2f\x5b\xa9\xe3\xfc\x7e" "\x9d\x75\x37\x00\x7f\x37\x42\x4d\x04\xf0\xcb\xba\x8e\xc7\xad\xe6\xdd\xcd\x78\xe0\xe9\x4a\xb7\x30" "\x46\xa8\x61\x46\xa8\x43\x70\xee\xc5\xd9\xbe\xda\xdd\xdc\x0e\x7c\xc9\x47\x28\x56\x7c\xf6\x2d\x68" "\x27\xeb\x68\x36\xc7\x09\xe7\x32\x30\xee\x45\x93\x97\x0a\x35\x2d\x53\xc7\x73\x74\xd1\x8e\x1b\x67" "\xde\x83\xf3\x25\xde\x1f\xb7\x3a\x4b\xf6\x14\xe2\x72\x5c\xcc\xd2\x8b\x51\xca\xe0\x0d\x2b\x37\xe0" "\x26\x0f\x26\x91\x7f\xd8\x78\x8d\xb4\x7a\x46\x9c\x65\x13\xb4\x06\xbf\x4c\xe1\x58\xd5\xc3\xe2\x7c" "\x00\x80\x11\x56\x7f\x2f\xd5\xc9\x7e\x23\x5c\x0f\x7e\x3d\xce\x54\x39\x1e\x97\x35\xfe\x70\xe0\x85" "\xac\xc3\x87\x01\x7f\x30\x42\x3d\xe0\x83\xd5\x42\x63\x84\xda\x15\x97\x49\xe0\x72\xe0\x6c\xb6\x56" "\x6e\x0b\x2e\x7d\xe3\x8c\xb8\xcb\x26\xb0\x39\xcf\x67\x88\x39\x32\xcf\xae\x67\xa5\xd5\xc7\xc5\xfd" "\x20\x19\x66\xd4\x2d\x67\xe1\xc0\xe1\x3c\xf1\x91\x11\xea\x0c\x60\x27\x5c\x74\xff\x77\x70\xd1\x0e" "\xb3\x81\x2f\xe6\x9c\xd6\x81\xcb\x00\x7b\x73\xb9\xc1\xea\x3e\x3f\x98\x04\xce\x02\xa6\x52\xd8\x3b" "\xf2\x45\x5c\x8b\x51\x96\x49\x33\x2a\xc2\x28\xf8\xfb\xb8\x74\x44\xb9\xbc\x0d\x1c\x2e\xad\x0e\x65" "\x24\x0f\x8b\x11\xea\x44\x5c\x6a\xc5\x4f\xe1\xac\x5a\xef\xe2\x1c\xe4\xa7\xe7\x39\x3c\x83\x9b\x95" "\x7a\x02\x78\x12\xb7\xd8\x56\x3a\xe7\x7a\xfd\x81\xcf\xe0\x26\xe8\xff\x11\x97\x62\xa1\x98\x4f\x73" "\x2b\xf0\x3d\xe0\xba\x38\x56\x19\x2d\x44\x18\x05\x2b\x5c\x4a\xbe\x5c\xd6\x03\x7b\x56\xc3\x5a\x0b" "\x46\xa8\x51\x38\x67\xf4\x93\x71\xf9\x39\x6e\xc7\x59\x92\x4a\xc5\x2f\xb7\xe1\x9a\xd5\x4d\xfe\xd3" "\x81\xf3\xbf\xee\x89\x87\x63\xbb\xbf\xfe\x4c\xdf\xa3\x4f\x94\x30\x0a\xde\x85\xc2\x3d\xe6\xaf\x49" "\xab\xff\x33\xe9\x87\xf3\x72\xf6\xc7\x2d\xa7\xf7\x2d\x9c\x73\xc0\xf9\xb8\xf7\xe4\x48\xe0\x28\x8a" "\x67\x2a\x28\x87\x77\x70\xc3\x9f\xdb\xa3\x4c\x98\x16\x96\x50\x53\x6a\x46\xa8\x15\xe4\xf7\xa0\x58" "\x0a\x8c\xac\xa6\x64\xdf\x46\xa8\x63\x70\x59\x7f\xfe\x01\x97\xb2\x70\x3c\x2e\x4d\xd1\xe1\xb8\x31" "\xe9\xf1\xb8\x3e\x85\xe8\xe1\x25\x3b\x71\xcb\xeb\xfd\x0e\x37\xd6\x7d\xbe\x9a\x9e\xb7\x9b\xb0\x0a" "\xbe\x0f\x67\x39\xca\xc7\x38\x69\xf5\x53\x49\x3f\x60\x1e\x99\xc7\xe3\x82\xc3\x47\xe3\x9a\xed\x6b" "\xbb\xe7\x60\x7d\xaa\xc3\x23\x71\x8e\x7b\xa9\x02\x9f\x34\x2e\xaf\xc6\x6b\x55\x9e\xfb\x03\x08\xaf" "\xe0\xa9\x40\xa1\xa6\x78\x09\x2e\x6d\x7f\xec\xcb\xbe\xf6\x40\xee\x14\xae\xa3\x34\x05\xd7\x02\x3d" "\x8a\xeb\xed\xbe\x24\xad\xfe\x6b\xd2\xf2\x45\x49\x58\x05\xef\x80\x5b\xdb\xa7\xd8\x32\x6c\xeb\x80" "\x19\xd2\xea\xdb\x93\x7e\xd8\x02\xcf\x30\x04\x17\x3c\x3e\x01\xe7\x57\x6d\x71\x4d\xf7\x4b\xb8\x2c" "\x3d\xe9\xac\xcf\x6a\x60\x61\xa5\x97\x65\x8f\x92\xd0\x6e\x2d\x46\xa8\x7b\x29\x9d\xd2\x10\x5c\xae" "\xaa\xe7\x7a\x70\x5c\xa2\x18\xa1\xf6\x00\x0e\xc6\x8d\xa3\x1b\xfc\xa7\x0d\x58\x20\xad\x36\x49\xcb" "\x57\x2e\x51\x28\xf8\x70\xb6\x74\x7a\x2b\xc4\x0b\xd2\xea\xa3\x92\x7e\xe0\x6d\x8d\xd0\xb1\x49\xd2" "\xea\x85\x94\x5e\x64\x02\x9c\x3b\x6b\xa9\xfc\x5a\x7d\x44\x4c\x54\xc1\x67\x3f\xea\xc1\x31\x4f\x02" "\x57\x79\x13\x5f\x1f\x31\x11\x95\x82\xef\x67\xeb\x10\x92\x5c\x76\xc7\x4d\x02\xec\x9f\xf4\x43\x6f" "\x4b\x44\xa2\x60\x3f\x1e\x9c\x53\xe2\xb0\x31\x40\x46\x5a\xbd\xac\xf4\x15\xfb\x88\x8a\x28\xe3\x83" "\xef\xa4\xb4\x2f\xd1\x1d\x71\x38\xa4\xf7\xb1\x99\xc8\x14\xec\xbd\x28\xfe\xb7\xc4\x61\x63\x71\x89" "\xbd\xfa\x88\x89\xa8\x23\xfc\x7b\x92\x1f\xf2\xc6\x38\x82\xc3\xfa\x70\x44\xaa\x60\x9f\x53\xa2\x54" "\x30\xd6\x7a\x42\xa4\xe6\xeb\xa3\x3c\xa2\xae\xc1\xe0\x26\xd7\x3f\x2c\xb2\x7f\x27\xb6\x4c\x7d\xd4" "\x47\x05\x89\x5c\xc1\xde\x93\xe3\x8a\x22\x87\x34\x03\xbf\x30\x42\x6d\xdf\xc3\x4b\xf6\x11\x82\x4a" "\xd4\x60\x80\xbb\x29\x6e\xbe\x94\xc0\xe4\xbe\x1e\x75\xe5\xa9\x88\x82\xfd\xc4\xf7\xd7\x28\xfe\xae" "\x1d\x5e\x8d\x13\xe4\xbd\x8d\x4a\xd5\x60\x7c\x10\x5a\xb1\x35\xeb\x2f\x32\x42\xc5\x16\x05\xb1\xad" "\x52\x31\x05\x7b\xae\xc6\xcd\xa1\xe6\xa3\x01\xb8\xd3\xa7\x12\xea\xa3\x42\x54\xb4\x70\x7d\xdc\xcd" "\xc5\x45\x0e\x39\x01\xf8\xb7\xa4\x0b\xa1\x37\x13\x4b\x27\xc7\x08\x75\x23\x85\x15\x99\xc6\xad\x4a" "\x16\x78\x11\xe4\x3e\x0a\x13\x57\xf3\x78\x05\x2e\xb4\x23\x1f\xf5\xb8\x18\xde\x52\x89\xb1\xfb\x08" "\x40\x2c\x0a\xf6\x49\x37\xcf\xc6\x45\x0f\xe4\x63\x3f\xdc\xfb\x78\x5c\xd2\x05\xd2\xdb\x88\x75\x1c" "\xea\x73\x54\xbc\x88\x0b\xfb\xc8\x87\x05\x76\x97\x56\x7f\xd8\xf3\xab\xf6\x51\x8c\x58\x7b\xb0\xd2" "\xea\x55\xc0\xe9\xb8\xf0\x8e\x7c\x08\xbf\xbf\x8f\x88\x48\xc4\x92\xe4\xd3\x1a\x3c\x50\x60\xf7\x5f" "\x71\x2b\x6e\x26\x1a\xbc\xd6\x5b\x48\x64\x0c\x2a\xad\x7e\x10\x98\x51\x60\xf7\xce\xb8\x00\xee\x3e" "\x22\x20\x51\x5b\xb0\x11\xea\x36\xa0\x50\x2e\xab\xa9\xc0\x83\xc0\xa6\x6a\x88\x54\xac\x55\x12\x37" "\xf6\x1b\xa1\xa6\xe1\x62\x84\x0a\xc9\xb2\x02\x17\x6f\x9c\xe8\x8a\x2a\xb5\x4a\xe2\x0a\x06\x30\x42" "\x4d\xc6\x2d\xea\x5c\x28\xb2\xef\x59\xe0\x44\x69\x75\x24\xd9\xdf\xb6\x25\xaa\xc2\x0e\x2c\xad\x7e" "\x08\xb7\x82\x59\xa1\xe1\xd1\xb1\xc0\x03\x7d\xae\x3e\xe5\x53\x15\x35\xb8\x1b\x23\xd4\x01\xb8\x34" "\x0a\x85\x92\xa3\x6c\xc4\xa5\x45\xb8\xa9\x16\x42\x37\xab\x81\xaa\xa8\xc1\xdd\x78\x9f\xe9\x23\x70" "\x91\x7d\xf9\x68\x06\x4e\x23\xc2\x54\x7f\xbd\x9d\xaa\x52\x30\x80\x8f\xcf\x3d\x0e\x78\xa8\xc0\x21" "\xcb\xe9\xf3\xe9\xea\x31\x55\xd5\x44\xe7\x62\x84\x9a\x80\x4b\x9c\xb2\x57\xce\xae\x0e\xe0\x27\xb8" "\x44\x27\x2b\x93\x96\xb3\x9a\xa9\x6a\x05\x03\x18\xa1\x9a\x71\x46\x91\x4b\xd9\x3a\x81\x79\x07\xce" "\x28\x72\x03\xb0\x9f\x77\xdb\xed\x23\x8b\xaa\x57\x70\x37\x46\xa8\xee\x5c\xd0\x47\xe4\xd9\xdd\x8e" "\x4b\xd7\xbb\x50\x5a\x3d\xbd\xac\x0b\xf7\x72\x6a\x46\xc1\xf0\x71\x6e\x8d\xa9\xc0\x4c\x60\x70\x81" "\xc3\xae\x95\x56\x7f\x37\x69\x59\xab\x85\x9a\x52\x70\x37\x3e\x47\xd7\x0c\xdc\x1c\xf3\xa0\x3c\x87" "\xdc\x0a\x7c\xc3\xcf\x43\x6f\xd3\xd4\xa4\x82\xbb\xf1\xef\xe7\x49\xb8\xec\x75\x63\x73\x76\x3f\x09" "\x5c\x20\xad\x7e\xab\xec\x0b\xf7\x22\x6a\x5a\xc1\xd9\x78\x23\xc9\xb9\xb8\x84\x30\xdd\xeb\x49\x58" "\x5c\x2f\xfc\x7f\x70\x29\x92\x12\xcb\x19\x99\x14\xbd\x46\xc1\xdd\xf8\x14\x11\xe3\x81\x73\x70\x1d" "\xb2\x6e\xef\x91\x16\x5c\x47\xec\x69\xff\x59\xb4\x2d\x28\xbc\xd7\x29\x38\x17\x23\xd4\x8e\xc0\x81" "\x59\x9f\x11\xb8\xf7\x76\x17\x2e\x59\xdb\x62\xdc\xba\x09\x8b\xa5\xd5\x49\xad\xa0\x5a\x31\xfe\x1f" "\x84\x22\x6b\xc8\x75\xb1\x83\x37\x00\x00\x00\x25\x74\x45\x58\x74\x64\x61\x74\x65\x3a\x63\x72\x65" "\x61\x74\x65\x00\x32\x30\x31\x31\x2d\x30\x36\x2d\x31\x38\x54\x30\x30\x3a\x34\x37\x3a\x34\x36\x2b" "\x30\x32\x3a\x30\x30\x1b\xae\xff\xa0\x00\x00\x00\x25\x74\x45\x58\x74\x64\x61\x74\x65\x3a\x6d\x6f" "\x64\x69\x66\x79\x00\x32\x30\x31\x31\x2d\x30\x36\x2d\x31\x38\x54\x30\x30\x3a\x34\x37\x3a\x34\x36" "\x2b\x30\x32\x3a\x30\x30\x6a\xf3\x47\x1c\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61" "\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00" "\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; /* Small Debian JPEG logo (without "Debian") */ unsigned char jpeg_sm[] = "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x02\x00\x0f\x00\x0f\x00\x00\xff\xdb\x00\x43" "\x00\x03\x02\x02\x02\x02\x02\x03\x02\x02\x02\x03\x03\x03\x03\x04\x06\x04\x04\x04\x04\x04\x08\x06" "\x06\x05\x06\x09\x08\x0a\x0a\x09\x08\x09\x09\x0a\x0c\x0f\x0c\x0a\x0b\x0e\x0b\x09\x09\x0d\x11\x0d" "\x0e\x0f\x10\x10\x11\x10\x0a\x0c\x12\x13\x12\x10\x13\x0f\x10\x10\x10\xff\xdb\x00\x43\x01\x03\x03" "\x03\x04\x03\x04\x08\x04\x04\x08\x10\x0b\x09\x0b\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10" "\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10" "\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\xff\xc0\x00\x11\x08\x00\x30\x00\x30\x03" "\x01\x22\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1b\x00\x01\x00\x02\x03\x01\x01\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\x07\x06\x08\x00\x01\x04\x02\x09\xff\xc4\x00\x30\x10\x00\x01\x04\x01\x03" "\x04\x00\x05\x03\x03\x05\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x11\x00\x06\x07\x08\x12\x21\x31" "\x13\x32\x41\x51\x71\x14\x15\x42\x09\x22\x33\x52\x62\x81\x91\xd1\xff\xc4\x00\x1a\x01\x00\x02\x03" "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x03\x04\x05\x02\x06\xff\xc4\x00\x29" "\x11\x00\x02\x01\x03\x02\x05\x02\x07\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x11\x00\x03\x05\x04" "\x21\x06\x12\x31\x41\x51\x71\x81\x13\x15\x22\x24\x32\x72\xc1\xff\xda\x00\x0c\x03\x01\x00\x02\x11" "\x03\x11\x00\x3f\x00\xfa\xa7\xa0\x8e\xa5\x7a\xb1\xda\x9d\x3e\xb3\x1e\xa1\xb8\x1f\xbe\x6e\x79\xcd" "\x17\x99\xae\x43\xdf\x0d\x0c\x35\xe4\x25\xd7\x97\x83\x84\x95\x0c\x04\x81\x95\x60\xfa\x1e\x74\x9b" "\xca\x3b\xe6\x07\x1a\x71\xfd\xe6\xfa\xb2\x59\x0c\xd3\x44\x54\xa0\x81\x81\xf1\x96\x9f\x91\x9c\x90" "\x40\x2e\x2b\x08\x07\xe8\x56\x3e\xba\xab\x3d\x32\x74\xef\x0f\x98\x25\xcc\xea\x57\x9d\x21\x31\x79" "\x37\x74\xca\x76\x65\x65\x54\x80\x57\x19\xa6\xbb\xca\x42\xdc\x42\xbc\x2c\x60\x76\xb6\xd9\xca\x52" "\x84\x82\x72\x48\xed\x63\xc9\xaf\x67\xc3\x18\xbc\x70\xb3\x73\x33\x9c\x93\xa6\xb6\x42\x85\x5d\x8d" "\xcb\x84\x48\x50\x76\x80\x06\xec\x64\x6d\xeb\x46\x15\x74\xfd\x6e\xf5\x53\x30\x6e\x84\x5c\xd8\xed" "\xda\x5e\xe0\xf4\x37\x1c\x92\xed\x4c\x00\x0f\x94\x96\x50\x80\x5c\x78\x63\xd3\x98\x5f\xd8\xab\x53" "\xe1\xd3\xe7\x5e\x3b\x30\xc7\xb8\xdb\x7c\xe6\xcd\xcc\x88\xd9\x3f\xa4\x72\xe5\xf7\x52\xb2\x71\x9c" "\xa2\x53\x7f\x09\xcc\xe3\xda\xfc\xff\x00\xd9\xd5\xd3\xb5\xb5\xa8\xdb\x75\x12\x6d\xed\xe6\x47\x81" "\x5d\x5e\xc9\x75\xf7\x9d\x50\x43\x6c\xb6\x91\xe4\x9f\xb0\x03\x51\xca\x5e\x4b\x89\x7a\xfb\x6a\x89" "\xb4\x37\x5b\x35\xcf\x63\xe1\x59\x4a\xa9\x5b\x0c\xac\x1f\x47\xe1\xac\x87\xd2\x3f\xdc\xa6\x80\xc7" "\x92\x40\xf3\xa7\x35\xb9\x73\x8f\x72\x3a\x85\xfb\x2d\x0d\x94\xd3\xae\xc1\x0d\xb0\xcb\xe8\x49\x89" "\x27\xb8\x10\x4f\x8a\xa9\x7b\x3b\xae\x5e\x4c\xe3\xad\xf6\xe6\xc3\xea\x67\x65\xa6\x0e\x1d\x4a\x1d" "\x97\x16\x31\x65\xe8\x89\x3e\x03\xa5\xb0\x54\x97\xda\x38\xcf\x72\x0f\xac\x94\xf7\x78\x1a\xbb\x95" "\xb6\x55\xf7\x35\xd1\x6d\xea\x66\x33\x2e\x14\xd6\x51\x22\x3c\x86\x56\x14\x87\x5b\x58\x05\x2b\x49" "\x1e\x08\x20\x82\x0e\x80\x7a\xda\xe1\xaa\x7e\x4b\xe1\xeb\x4d\xd0\xd4\x46\xd3\x7f\xb4\x62\xb9\x67" "\x0e\x52\x51\xfd\xea\x8e\xd8\x2a\x7d\x92\x7d\x94\x94\x05\x28\x0f\xa2\x92\x31\xec\xe7\x7d\x08\x17" "\xcf\x4f\x55\x2a\x37\x12\xa7\xc4\x54\x97\xcc\x54\xc8\x6c\x25\x51\x40\x21\x2e\x32\x08\x27\xb9\x01" "\xe4\xba\xa4\x9f\xf4\xad\x39\xc1\xc8\x08\xf4\x9a\xad\xc4\x1a\x6c\x3e\x63\x04\x99\xfc\x7d\xa1\x62" "\xea\xb8\xb7\x72\xda\xcf\x21\x24\x48\x65\xf1\x20\x74\xe9\xd7\xb8\x96\xe1\xfe\xa0\x29\x96\xf7\x00" "\x18\x31\x1f\x5a\x0c\xcb\xd8\x0c\x29\x09\x38\x0e\x82\x56\x42\x55\xf7\x1d\xc1\x27\xf2\x91\xab\x0d" "\x45\x4b\x5f\xb7\x29\x2b\xf6\xf5\x4c\x74\xb1\x0a\xb2\x2b\x50\xe3\x34\x91\x80\x86\x9b\x48\x4a\x52" "\x3f\x00\x0d\x07\xf5\xcb\x4b\x3a\xd7\xa7\x4b\xc9\xb5\xd9\xf8\xf4\xb2\xa1\x59\x80\x06\x4f\x6a\x1e" "\x4a\x54\x47\xe0\x2c\xab\xf0\x93\xa5\xed\x83\xbb\x6b\xb7\xde\xcb\xa4\xdd\xf5\x53\x19\x95\x1e\xd6" "\x0b\x32\x82\xda\x20\x80\xa5\x20\x15\x24\x80\x4f\x6a\x81\x24\x14\xfb\x04\x10\x7d\x68\xed\x58\xba" "\xc2\xef\xc3\x1a\x42\x9f\x8a\xde\xbc\x1b\xf6\x2b\x68\x89\xf6\x98\xf7\xa8\x86\xfe\x96\xd5\x8f\x31" "\xf1\xe6\xcb\xb4\x58\x35\x92\x18\xb4\xbb\x11\xd4\x9c\xa6\x4c\xd8\x7f\xa7\x11\xc2\xbe\x84\x20\x3e" "\xeb\xa0\x1f\xe6\xdb\x6a\xf6\x91\xa4\xfd\x1a\xf3\x5e\xc2\xbf\xdd\x55\xb0\x37\x26\xcd\x98\x62\xee" "\x9d\xaa\xff\x00\xee\x34\x6e\xa5\x09\x38\x78\x82\x87\x5a\x58\x51\x01\x6d\x3a\xd2\x94\x85\x27\x20" "\xfa\x20\xe4\x63\x51\x5a\x5e\xa3\x91\x35\xa6\x86\xef\xe2\x3e\x44\xae\xbe\x80\xa5\xa5\x50\xab\x6a" "\xe4\x4d\x65\xe7\x06\x50\xae\xd2\xd1\x1d\xc9\x24\x1c\x07\xd2\x82\x3c\x1c\x7d\x74\x54\x1f\x2e\xbb" "\x94\xd0\xd8\x7d\x1f\xd5\xf0\xc1\x57\x50\x47\x30\x62\xcc\xc1\xa2\x44\x82\x08\x12\x26\x39\x4c\xec" "\x16\x51\xb9\x56\xc1\x9a\xfe\x2b\xde\x16\x72\x64\x30\x96\x59\xa2\x9e\xe8\x5a\x95\xda\x8e\xdf\xd3" "\xaf\x19\x3f\xf9\xa3\x2e\x88\x36\x7d\xc6\xce\xe9\xe6\x89\xab\x97\x12\x57\x6e\xe3\x96\xec\x36\x07" "\xf8\xe3\xbe\x12\xa6\xc1\xf0\x32\x4a\x70\xaf\x39\xf9\xf1\x9c\x01\x8f\x37\x1b\x6b\x92\xba\x8c\x7e" "\x3d\x4e\xf4\xda\xeb\xd9\x1c\x69\x1e\x53\x52\x64\x55\xcd\x53\x6e\x5a\xde\x96\x96\x95\xb6\x87\x50" "\x8e\xe4\x45\x63\xb9\x20\xa9\x3d\xc5\x67\xb7\xe8\x0e\x43\xb4\x18\x30\xeb\x21\x47\xad\xaf\x8c\xdc" "\x78\xb1\x5a\x43\x0c\x34\xd8\xc2\x5b\x6d\x20\x04\xa4\x0f\xa0\x00\x01\xa3\xb4\x54\x9a\xbb\xe3\x17" "\x88\x38\x9e\x60\xd7\x2e\x38\x77\xe5\x21\x82\x85\x04\x2a\xf3\x09\x05\x8f\x31\x24\x09\x81\x00\x99" "\x90\x39\xef\xa8\xeb\x77\x35\x1d\x86\xdc\xb9\x8e\x1f\x81\x69\x15\xd8\x72\x9a\x3e\x96\xd3\x89\x29" "\x50\xff\x00\x90\x4e\xaa\x67\x48\x22\xc7\x82\x39\x43\x7a\x74\xd1\xbd\x25\x06\xd6\xf3\xe2\xe7\x6f" "\x3e\xe6\x50\x9b\x06\xfb\x7b\x56\xa6\xf3\xe0\x95\x36\x96\xd5\xda\x3d\x16\xdd\x1e\x7b\x4e\x2e\x16" "\x8d\xb9\xaf\x83\xb6\xf7\x32\xd5\x42\x5c\x89\x4e\xd4\x6e\x3a\x37\x84\xca\x2b\xc8\xc9\x05\xf8\x12" "\x12\x42\x92\x71\xfc\xd1\xdc\x94\x92\x83\xf6\x04\x10\x40\x3a\x01\xae\x70\x39\x6b\x36\x34\xf7\xf1" "\x3a\xe2\x46\x9e\xf8\x12\x40\x92\x8e\xa6\x55\xc0\xee\x07\x46\x03\x72\xa7\x6d\xc0\x14\x93\xad\x14" "\xa4\xa8\x2c\xa4\x15\x27\x38\x38\xf2\x34\x41\xb2\x79\x33\x7d\xed\x21\x17\x67\xf3\xe5\x13\xd1\xec" "\x5b\x25\xa4\x6e\xba\xf8\xdd\xd4\xb3\x80\xf9\x16\xe2\xd3\xe6\x2b\x8a\x1e\xc3\x88\x42\x3b\xb3\xda" "\x40\x20\x69\x62\x0d\x84\x0b\x48\xc9\x99\x5b\x3a\x3c\xb8\xeb\xf9\x5d\x61\xd4\xb8\x83\xf8\x52\x49" "\x1a\x55\x8f\xae\xc7\x5e\xd0\x3c\x3c\x15\xec\xca\x65\x58\x79\x07\xf9\xb1\x1d\x08\x07\x6a\xe8\xd6" "\x6b\x35\x9a\x2a\x85\x7f\xff\xd9"; /* Large Debian JPEG logo (without "Debian") */ unsigned char jpeg_lrg[] = "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x02\x00\x27\x00\x27\x00\x00\xff\xdb\x00\x43" "\x00\x03\x02\x02\x02\x02\x02\x03\x02\x02\x02\x03\x03\x03\x03\x04\x06\x04\x04\x04\x04\x04\x08\x06" "\x06\x05\x06\x09\x08\x0a\x0a\x09\x08\x09\x09\x0a\x0c\x0f\x0c\x0a\x0b\x0e\x0b\x09\x09\x0d\x11\x0d" "\x0e\x0f\x10\x10\x11\x10\x0a\x0c\x12\x13\x12\x10\x13\x0f\x10\x10\x10\xff\xdb\x00\x43\x01\x03\x03" "\x03\x04\x03\x04\x08\x04\x04\x08\x10\x0b\x09\x0b\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10" "\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10" "\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\xff\xc0\x00\x11\x08\x00\x78\x00\x78\x03" "\x01\x22\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1c\x00\x01\x00\x02\x02\x03\x01\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\x00\x08\x09\x04\x07\x03\x05\x06\x02\xff\xc4\x00\x43\x10\x00\x01\x03\x03" "\x03\x02\x03\x04\x07\x03\x08\x0b\x00\x00\x00\x00\x01\x02\x03\x04\x00\x05\x11\x06\x07\x21\x08\x12" "\x13\x31\x41\x09\x22\x51\x61\x14\x15\x23\x32\x42\x52\x72\x62\x71\x91\x17\x24\x27\x33\x43\x73\x82" "\xa1\x25\x34\x63\x66\x81\x83\xa4\xb1\xb4\xc1\xc3\xff\xc4\x00\x1c\x01\x00\x02\x03\x01\x01\x01\x01" "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x01\x06\x07\x05\x04\x08\x03\xff\xc4\x00\x37\x11\x00" "\x01\x02\x04\x04\x04\x05\x01\x04\x0b\x00\x00\x00\x00\x00\x00\x01\x00\x02\x03\x04\x05\x11\x06\x21" "\x31\x41\x07\x12\x51\x71\x13\x22\x61\x81\x91\x72\x14\x32\x52\xc1\x15\x17\x23\x24\x33\x36\x42\x53" "\x62\xa1\xb1\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00\x3f\x00\xb5\x3a\x52\x94\x21\x29\x4a" "\xf0\xbb\xc5\xbc\x9a\x2f\x64\x74\x83\xda\xb7\x58\xcd\xed\x4f\x2d\xc3\x86\xd9\x05\xf9\xaf\x63\x21" "\xb6\xd2\x7c\xfe\x6a\x3c\x24\x72\x4d\x0b\xd1\x29\x29\x1e\x7a\x3b\x65\xa5\x98\x5c\xf7\x1b\x00\x33" "\x24\xaf\x6b\x26\x4c\x68\x51\xdd\x99\x32\x43\x4c\x30\xca\x0b\x8e\xba\xea\xc2\x50\x84\x81\x92\xa5" "\x13\xc0\x00\x7a\x9a\x8b\x9b\xc3\xed\x03\xda\xfd\x0a\xeb\xf6\x6d\x01\x15\x7a\xca\xe8\xde\x52\x5f" "\x61\xdf\x0a\xde\xda\xbf\xbe\xc1\x2e\xe3\xcf\xdc\x49\x49\xfc\xe2\xa1\x9e\xf6\xf5\x5b\xba\xdb\xe2" "\xda\xed\x37\xcb\x8b\x76\xdb\x07\x8e\xa7\x5b\xb5\x40\x05\x0d\xa8\x77\x65\x01\xe5\x7d\xe7\x8a\x46" "\x31\x9e\x32\x32\x12\x0d\x69\xb4\xa5\x4a\x50\x4a\x41\x24\x9c\x00\x3c\xc9\xa7\x0d\xea\xbe\x85\xc2" "\xdc\x19\x81\x05\xa2\x63\x10\x3b\x99\xdf\xdb\x69\xb3\x47\xd4\xe1\x62\x7b\x0b\x0f\x52\xb7\xae\xae" "\xeb\x67\xa8\xad\x57\x75\x76\xe0\xc6\xb8\x55\x89\x85\xa5\x4d\xa2\x15\xa9\x84\x34\xcb\x69\x24\x1e" "\x0a\x82\x96\x4f\x03\xde\x2a\x24\x73\x8c\x64\xd6\x76\x9d\xeb\x97\x7d\xec\xac\xb7\x06\xe1\x78\x8b" "\x79\x80\xd2\x3b\x7e\x8f\x39\xae\xf5\x38\x7f\x32\xdf\xcf\x8c\x4f\xee\x58\xfe\x1c\x56\x36\xd1\x74" "\x59\xbd\x5b\xae\xc2\x6e\x8a\xb5\x37\xa6\x6d\x0a\xe5\x33\x2f\x29\x5b\x2a\x74\x7f\xb3\x64\x02\xe2" "\x87\xed\x10\x94\x9f\x45\x1a\xdc\xf0\x3d\x98\x77\x57\x14\xaf\xad\x37\x8a\x2b\x09\x00\x76\xf8\x16" "\x45\x3a\x4f\xc7\x39\x7d\x38\xff\x00\x3a\x9c\x95\x9a\xa5\x50\xe1\xed\x33\xf7\x19\xa1\x04\x72\xec" "\xd6\x73\x11\xee\xc6\x93\xcd\xef\x70\xbc\x03\xbe\xd1\x5d\xfb\x50\x69\x31\xed\xda\x4a\x32\x5a\x79" "\x2e\xf6\xb5\x6e\x74\x85\xa0\x24\x82\xd2\xbb\x9e\x27\xb4\x92\x0e\x41\x0a\xca\x47\xbd\x8c\x83\xba" "\x76\xb7\xda\x43\xa4\x2f\x2e\xb5\x6d\xdd\x7d\x2e\xf6\x9f\x75\x58\x4f\xd6\x36\xe2\xa9\x31\x73\xea" "\x56\xde\x3c\x54\x0f\xd3\xe2\x57\x98\xba\x7b\x30\xee\x0d\xb3\xdd\x65\xde\x28\xef\xbb\x8f\xb9\x2a" "\xc8\xa6\x92\x4f\xea\x4b\xca\x3f\xe5\x5a\x73\x71\x7a\x1a\xdf\xed\x02\xdb\xd3\x62\xd8\x23\xea\x78" "\x0d\x02\xb5\x3f\x65\x74\xbc\xb0\x91\xf1\x65\x41\x2e\x93\xfa\x52\xa1\xf3\xa8\xf2\x95\xc4\xfb\x2f" "\x0d\x31\x1b\x7e\xcb\x01\xcc\x86\xe3\xa1\x1c\xd0\x9d\x7e\xee\x00\x13\xe8\x41\xec\xac\x2a\xc3\xd4" "\xbe\xc3\xea\x7d\x43\x0f\x4a\xd8\x77\x36\xd1\x32\xe9\x70\x52\x51\x15\x84\x29\x7f\x6a\xb5\x7d\xd4" "\x05\x14\x84\xf7\x1c\xe3\xb7\x39\xcf\x18\xcd\x6c\xda\xa2\xbf\xe7\x30\xa4\xff\x00\x68\xc4\x86\x17" "\xf3\x4a\xdb\x5a\x4f\xf1\x04\x11\x52\x73\x60\xba\xe7\xdc\x3d\xbc\xbb\x31\x69\xdc\x79\xf2\x75\x46" "\x99\x7d\x69\x4b\xca\x7c\xf7\x4d\x86\x38\x05\xc6\x97\xe6\xbf\x89\x42\xf3\x93\x92\x0a\x49\x24\xc1" "\x6f\x45\x5d\xc4\x7c\x17\x8b\x2b\x03\xc7\xa2\x45\x31\x2c\x33\x6b\xed\xcc\x7e\x92\x00\x1e\xc6\xdd" "\xf6\x56\x71\x4a\xeb\x34\xce\xa6\xb0\x6b\x2b\x0c\x2d\x4f\xa5\xee\xb1\xee\x56\xbb\x8b\x41\xe8\xd2" "\x58\x56\x50\xb4\x9f\xf3\x04\x1c\x82\x0e\x08\x20\x82\x01\x18\xae\xce\x95\x61\x91\x21\xbe\x13\xcc" "\x38\x82\xc4\x64\x41\xc8\x82\x36\x29\x4a\x52\x84\x89\x4a\x56\x05\xf6\x25\xce\x7d\x92\x7c\x1b\x2d" "\xd3\xea\xdb\x84\x88\xce\x35\x16\x6f\x84\x97\x7e\x8a\xea\x92\x42\x5d\xec\x57\xba\xb2\x92\x42\xbb" "\x4f\x07\x18\x3c\x1a\x13\x31\xa1\xce\x0d\x26\xd7\xdc\xed\xf1\x72\xb8\x35\x5e\xa8\xb2\x68\x9d\x35" "\x72\xd5\xba\x92\x68\x89\x6c\xb4\xc6\x5c\xa9\x4f\x10\x4f\x6a\x12\x32\x70\x07\x24\x9f\x20\x07\x24" "\x90\x07\x9d\x54\x06\xfb\x6f\x3e\xa4\xdf\x1d\x7f\x3b\x57\x5e\xe4\xba\x21\x87\x16\xd5\xaa\x12\x95" "\xee\x42\x89\xdc\x7b\x10\x00\xe3\xb8\x8c\x15\x2b\xf1\x2b\x3e\x98\x02\x52\xfb\x40\x3a\x89\xb6\x4e" "\x86\xe6\xc2\xe9\x69\x2f\xb9\x22\x34\xb6\x9d\xd4\x12\x30\x03\x64\x21\x29\x71\xb8\xe9\x23\xef\x1e" "\xe5\x25\x4a\xe0\x60\xb6\x91\xf1\x03\x4f\x74\x9f\xd2\x9d\xcf\x7d\xaf\x07\x51\x6a\x54\xc9\x81\xa2" "\xad\xce\xf6\xc8\x90\x8f\x75\xc9\xee\x8f\xec\x19\x27\xc8\x0f\xc6\xbf\x41\xc0\xe4\xf0\xed\xc8\x5c" "\xaf\xa2\x78\x73\x48\x93\xc2\x14\x78\x98\x9e\xb5\xe4\x73\xc7\x96\xfa\x86\x6d\xca\x35\xe6\x79\xd3" "\xab\x6d\xb1\x2b\x5a\xed\x5e\xc7\xee\x4e\xf0\xdc\x51\x1b\x46\x69\x6b\x84\xc8\x48\x79\x2d\x4b\x9e" "\x86\x80\x62\x38\x24\x67\x2b\x59\x4a\x0a\x80\x39\xec\xee\x04\xfe\xee\x6a\xcb\x36\xc3\xa3\xbd\x8e" "\xdb\x38\x70\x96\xde\x96\x6e\xf5\x78\x8c\x02\x97\x74\xb9\x12\xe3\x8b\x73\x1c\xa9\x28\xcf\x63\x63" "\xcf\x00\x0f\x2f\x32\x7c\xeb\x6d\xe9\x9d\x2f\xa7\xb4\x65\x8a\x1e\x99\xd2\xb6\x88\xd6\xcb\x5c\x06" "\xc3\x71\xe2\xc7\x47\x6a\x10\x3f\xee\x49\x39\x24\x9c\x92\x49\x24\x92\x6b\xb4\xa8\x2e\xba\xcf\xf1" "\x87\x13\x2a\x58\x91\xfe\x14\xa9\x30\x60\x0b\xe4\xd2\x41\x70\xff\x00\x32\x35\xec\x32\x17\xdf\x55" "\xf2\xdb\x68\x69\x09\x69\xa4\x25\x08\x40\x09\x4a\x52\x30\x00\x1e\x40\x0a\xfa\xa5\x29\x56\x68\x94" "\xa5\x28\x42\x8e\x3d\x54\x74\x8f\xa7\x77\xa2\xd1\x2b\x54\xe9\x28\x31\xad\xba\xe6\x3a\x3b\xda\x7d" "\x18\x6d\xbb\x90\x48\xfe\xa9\xff\x00\x4e\xe2\x38\x4b\x87\x90\x70\x09\xed\xf2\xab\xfb\xad\xaa\xe5" "\x63\xb9\xca\xb3\x5e\x20\xbf\x0a\x74\x27\x96\xc4\x98\xef\xa0\xa1\xc6\x9c\x49\xc2\x92\xa0\x79\x04" "\x11\x57\x9d\x50\x8b\xda\x23\xb1\x31\xa5\x5a\x58\xdf\x3d\x3b\x0c\x22\x5c\x35\x37\x0a\xfc\x96\xd1" "\xfd\x6b\x2a\x3d\xac\xc8\x38\xfc\x49\x51\x4b\x64\xf9\x90\xa4\x7a\x26\x99\xa7\x65\xb9\x70\xab\x1e" "\x46\x96\x99\x65\x06\xa0\xee\x68\x6f\xca\x19\x27\x36\xbb\x66\xfd\x27\x41\xd0\xe5\xa1\xcb\x4c\xf4" "\x57\xd4\x8b\x9b\x3f\xac\x93\xa3\xb5\x55\xc1\x43\x47\xea\x17\x92\x87\x4b\x8a\xf7\x2d\xf2\x8f\x08" "\x90\x33\xc2\x50\x78\x4b\x9f\x2c\x2b\xf0\x60\xda\x1d\x53\x26\xca\xec\xd6\xa3\xdf\x3d\x62\xbd\x17" "\xa5\xee\x36\xd8\x73\x1b\x86\xe4\xe2\xb9\xee\x2d\x09\x53\x68\x52\x12\xa4\xa7\xb5\x2a\x25\x7e\xf8" "\x20\x1c\x0c\x03\xc8\xab\x55\xe9\xde\xe5\xaa\x2e\x9b\x31\xa5\x9c\xd6\x8c\x29\xab\xdc\x58\x66\x04" "\xd4\xaf\x3d\xfe\x24\x75\xa9\x93\xdf\x9e\x7b\xfe\xcf\xde\xfd\xae\xec\x64\x60\xd0\xe4\x9c\x66\xa4" "\x48\x41\x9c\x6d\x42\x55\xc0\x45\x36\x11\x1b\xbe\x60\x96\x3e\xde\xa1\xae\x07\xb0\xf7\xd8\xd4\xa5" "\x29\x56\x20\x95\xe5\x37\x2b\x5d\x31\xb6\xfa\x46\xf1\xac\xee\x31\x83\x90\x2c\xf6\xb9\x33\x9c\x3d" "\xd8\x2a\x79\x1d\x9e\x13\x23\xe6\xe2\x94\x40\x3f\x10\x2b\xd5\xd4\x58\xf6\x8c\x6a\x0b\xad\xa3\x62" "\x62\x5a\xe0\xa4\x88\xb7\xbb\xec\x68\x73\x57\xdd\xf8\x10\xdb\x8f\x25\x18\xf9\xad\x94\x9f\xf0\x7c" "\xea\x46\x65\x77\x70\xcd\x31\xb5\x9a\xc4\xb4\x83\xfe\xec\x47\x80\x7b\x6f\xef\x6b\xdb\xd5\x57\xad" "\xae\x36\xa2\xdd\x7d\xc9\x8e\xc3\x8d\x3b\x73\xbd\x6a\xab\xb8\x2e\xa5\x0b\xec\x53\xcf\x3e\xee\x56" "\x7b\x88\x50\x40\xca\x89\xee\x20\x84\x8e\x48\x20\x55\xcd\xe9\x4d\x2f\x63\xd1\x5a\x6e\xdd\xa4\xf4" "\xdd\xbd\xb8\x36\xcb\x5c\x74\x46\x8c\xc3\x7e\x48\x4a\x47\xa9\xf3\x24\x9c\x92\xa3\xc9\x24\x93\xc9" "\xaa\xfe\xf6\x6f\x6d\xcc\x6b\xf6\xe1\x5f\x77\x1a\x7b\x68\x5a\x74\xc4\x44\x46\x86\x14\x39\x12\x64" "\xf7\x02\xe0\xfd\x2d\xa1\xc4\xff\x00\xcc\xab\x17\xa9\x71\xd9\x69\x1c\x65\xad\xb6\x66\xa5\x0e\x8f" "\x03\x26\x40\x6e\x60\x69\xcc\xe0\x08\xcb\x4f\x2b\x6d\x6e\xe4\x25\x29\x4a\x55\x8d\x2e\xaf\x53\xea" "\x6b\x1e\x8d\xd3\xd7\x0d\x55\xa9\x6e\x0d\xc1\xb5\xda\xd8\x54\x99\x52\x1c\xf2\x42\x12\x3e\x03\x92" "\x4f\x90\x03\x92\x48\x03\x24\xd4\x53\x8d\xba\xdd\x50\x75\x3b\x3a\x4a\xb6\x3e\x14\x6d\x01\xa1\x9a" "\x70\xa1\xab\xfd\xd1\x9c\xc9\x98\x90\x70\x7b\x32\x95\x82\x78\x3c\x36\x9c\x24\x82\x0b\x99\xe2\xbb" "\x0d\xde\x7a\x7f\x51\x9d\x46\xc4\xe9\xcd\xa9\xef\xb3\xa2\x74\xa4\x56\xef\x3a\xa9\x31\xcf\x69\x98" "\xf0\xed\x52\x18\x52\xc7\xe1\xfb\x46\x86\x01\xc8\x2a\x70\xe3\x28\x4e\x25\x4c\x08\x30\xad\x70\x63" "\xdb\x6d\xb1\x1a\x8b\x12\x23\x48\x61\x86\x1a\x40\x42\x1a\x6d\x20\x04\xa5\x29\x1c\x00\x00\x00\x01" "\x53\xa2\xbb\x43\xfb\x36\x15\x94\x85\x1a\x2c\x16\xc5\x9b\x8c\xd0\xf0\x1e\x39\x99\x0d\x87\xee\x92" "\xd3\x93\x9e\xed\x45\xee\x1a\xdb\x64\x49\x51\xe6\xcd\xd3\x36\xf3\xc1\x28\x9b\x37\xab\x9d\x6a\xec" "\xe2\x42\xdc\xed\x8e\x15\x1f\xbb\xe0\x96\x9c\x71\x49\xc7\xcb\x18\x3f\x0f\x4a\xd9\x9a\x4d\x7b\xb7" "\xa5\x16\x9b\x6e\xe1\xcd\xb6\x6a\xc8\x0a\x58\x6d\xbb\xdd\xb2\x21\x89\x29\xac\xf9\x19\x31\x72\xa4" "\x14\xe7\x82\xb6\x95\xc6\x46\x50\x13\xdc\xb1\xb0\x69\x45\xd7\x16\x73\x10\x4d\xd4\x41\x6c\xe3\x58" "\xe1\xe9\x0d\x8d\x23\xb1\x63\x5a\x47\x6b\xdb\xa8\x29\x5d\x16\xbb\xd2\x90\xb5\xd6\x8b\xbe\xe8\xdb" "\x88\x06\x3d\xea\xde\xfc\x15\x92\x33\xdb\xe2\x20\xa4\x28\x7c\xc1\x20\x8f\x98\x15\xde\xd2\xa1\x71" "\xe0\xc5\x7c\x08\x8d\x8b\x0c\xd9\xcd\x20\x83\xd0\x8c\xc2\xa4\xad\x05\x2b\x50\xd9\xb5\xf5\x8d\xdd" "\x3f\x24\xc3\xbb\xb1\x74\x8e\x98\xea\x2f\x78\x20\x3c\x1d\x4f\x6a\x54\xbf\xc2\x3b\x80\x04\x9e\x3e" "\x3c\x55\xd4\xdb\x59\x6d\x86\x96\x19\x69\x4d\x36\xf2\xcc\x80\x85\x0c\x14\x97\x0f\x72\xb2\x3d\x09" "\x59\x51\x3f\x33\x54\xc3\xbb\x69\x11\xf7\x6b\x59\xa5\xa0\x10\x1a\xd4\x77\x10\x90\x9e\x31\x89\x2e" "\x63\x18\xf2\xab\x91\xd1\xb7\xe8\x1a\xa7\x49\x59\xb5\x25\xa9\xa9\x4d\xc2\xba\x41\x62\x5c\x64\xca" "\x04\x3b\xe1\x2d\x01\x48\xee\xee\x24\xe4\xa4\x83\xcf\x34\xee\x5b\xc7\x1a\x83\xa3\xc1\x90\x9d\x0d" "\xb0\x70\x75\xfd\xc3\x08\x04\xfc\xdb\x2e\xbd\x57\x73\x4a\x52\x91\x60\x69\x51\x37\xda\x52\x7f\xa0" "\xcb\x10\xff\x00\x7b\x23\x7f\xe1\xcc\xa9\x65\x51\x2f\xda\x52\x7f\xa1\x0b\x02\x7e\x3a\xae\x39\xff" "\x00\xa3\x97\x52\x35\x57\x1e\x1f\x7f\x33\xc9\x7d\x7f\x91\x58\x1e\xcd\x0b\x67\x83\xb5\xda\xaa\xf3" "\xdb\x8f\xa5\x5f\xc4\x6c\xfc\x7c\x28\xed\xab\xff\x00\xb5\x4c\x3a\x8b\x5e\xce\x52\x83\xb0\x33\x3b" "\x52\x01\x1a\x8e\x5f\x76\x3d\x4f\x83\x1f\xff\x00\x58\xa9\x4b\x41\xd5\x4f\x10\x62\x18\xb8\x9e\x75" "\xce\xfc\x76\xf8\x00\x7e\x49\x4a\x52\xa1\x53\x54\x38\xe8\x3e\xe3\x1a\xeb\xb8\x5b\xcf\x74\x9d\x24" "\x39\x78\x9f\x7a\x6d\xe5\x87\x09\x53\xa1\xa0\xf4\x92\x49\x27\xd3\xb9\x60\x79\xfe\x11\xf0\xa9\x8f" "\x50\x2b\x64\x66\x7f\x21\x9d\x72\x6a\xfd\x01\x7b\x47\x81\x0b\x57\xbf\x25\xa8\x2e\x2c\xe1\x3f\x6c" "\xe0\x93\x14\xe7\xd7\x29\xcb\x5f\xad\x58\xf4\x35\x3d\x6a\x4a\xbe\xf1\x1a\x05\xab\x0d\x9b\x67\xf0" "\xe3\x43\x84\xf6\x1d\xb9\x79\x03\x72\xed\xca\x94\xac\x0b\xcd\xfe\xc5\xa7\x62\xa6\x6e\xa0\xbd\x40" "\xb6\x47\x52\xfb\x12\xf4\xc9\x28\x65\x05\x58\x27\x01\x4b\x20\x67\x00\xf1\xf2\xae\x34\xdc\x23\xea" "\x1b\x0b\x93\xb4\xb5\xe6\x14\x84\x4b\x65\x62\x24\xd6\x1c\x4b\xcc\x95\x72\x02\x82\x90\x70\xa0\x14" "\x3d\x0f\xa5\x42\xa3\x88\x2f\x2d\x0f\x22\xcd\x26\xd7\xb6\x5f\x2b\x99\x9b\x54\x66\x6f\x32\xaf\x88" "\x75\xf2\xfc\xb8\xcc\x44\x71\x05\xd2\x5b\x4a\x19\x53\xaa\x49\x4a\x3c\x92\xa2\x5f\x56\x4f\xa8\x09" "\xf8\x0a\xcd\xac\x3b\x4c\x47\xe0\xdb\xd9\x8f\x29\xf0\xf3\xe0\x15\xbc\xe2\x41\x09\x53\x8a\x25\x4a" "\xed\x04\x92\x13\x92\x70\x09\x38\x18\x19\x35\xa7\xba\xb5\xdf\x59\x3b\x13\xb6\x5f\x5b\x5a\x21\x26" "\x4d\xe2\xf7\x21\x56\xc8\x05\x4e\x76\x08\xea\x53\x4b\x51\x90\x78\x3d\xdd\x9d\xa3\xdd\xe3\x25\x43" "\x9c\x66\x8d\x57\xb6\x9f\x4e\x99\xac\xcf\x43\x91\x95\xf3\x3d\xe4\x34\x6d\xa7\x7d\x00\x03\xe0\x2a" "\xe8\xd3\xfa\x0a\x56\xf1\x75\x19\x23\x44\x7d\x3d\x98\xeb\xbe\x6a\x19\xde\x3c\x9e\x7b\x12\x80\xe3" "\x8e\x38\x53\xc1\xe4\x84\x90\x9f\x99\x19\xab\x7c\xb7\xb1\x16\x2c\x18\xf1\x61\x36\x96\xe3\xb2\xd2" "\x5b\x69\x08\xfb\xa9\x42\x46\x00\x1f\x20\x05\x41\x6f\x67\x96\xcd\x4f\x8f\x72\xbe\xee\xee\xa6\xb5" "\x3c\xd3\xac\x30\xdc\x1b\x3a\x25\x34\xa4\x95\x97\x90\x87\x95\x21\x39\x1c\x82\x85\x34\x12\xa1\x9c" "\x87\x17\x53\xb6\x3b\x22\x3c\x76\x98\x0a\x24\x36\x84\xa0\x13\xeb\x81\x8a\x67\x2d\x1b\x8b\x75\x96" "\x4e\xd4\xe1\xd3\x60\xba\xec\x97\x68\x6e\x5a\x73\x10\x0b\x8f\xc7\x28\xf4\x20\xae\x4a\x52\x94\xab" "\x27\x4a\x8c\xbe\xd0\xcb\x1b\xd7\x6e\x9e\x1c\x9e\xd3\x5d\xc9\xb3\x5e\xa1\x4e\x70\xfe\x54\xab\xbd" "\x8c\xff\x00\x17\xc0\xff\x00\x8d\x49\xaa\xf0\xdb\xe5\xa3\xd5\xaf\xb6\x7b\x58\x69\x26\x99\x2e\xbf" "\x70\xb4\x48\x4c\x64\x01\x9c\xc8\x4a\x0a\xd9\xe3\xfb\xc4\xa2\xa4\x6a\xbb\xb8\x62\x7c\x52\xeb\x52" "\xb3\x6e\xd1\x91\x1a\x4f\x6b\x8b\xff\x00\xab\xa8\xb1\xec\xc8\xd4\xb7\x17\xed\x1a\xe7\x47\xb8\xb6" "\xcc\x08\x52\x61\xdc\x99\x19\xf7\xc3\xaf\x25\x6d\xb9\xc7\xa8\xed\x61\xbf\xdc\x47\xce\xa7\x05\x55" "\x1f\x45\x3b\xb3\x66\xda\x6d\xea\x62\x56\xa7\x98\xcc\x2b\x3d\xf6\x1b\xb6\xa9\x72\xde\x5f\x63\x71" "\x8a\x8a\x56\xdb\x8a\x3e\x40\x77\xb6\x94\x92\x78\x01\x64\xfa\x55\xa9\x5a\xee\xb6\xbb\xdd\xbd\x8b" "\xb5\x96\xe5\x16\x7c\x19\x48\x0e\x31\x26\x2b\xc9\x75\xa7\x53\xf9\x92\xb4\x92\x14\x3e\x60\xd4\xb8" "\x66\xae\x9c\x5c\xa4\xc5\x91\xc4\x51\x26\xb9\x2d\x0e\x28\x6b\x81\xb6\x57\xb7\x29\x17\xeb\x76\x92" "\x77\xcd\x65\x57\x0c\xb4\x49\x76\x23\xcd\x43\x7d\x2c\x48\x5b\x6a\x4b\x4e\xa9\x1d\xe1\xb5\x91\xee" "\xa8\xa7\x23\x20\x1c\x1c\x67\x9a\xe6\xa5\x2a\xcb\xc1\xb1\xba\x88\xbd\x4a\x6c\xcc\x9d\xe9\xb1\x5b" "\xb5\x2d\xb5\x09\xb0\xee\x9d\x88\x14\xb4\xc9\x77\xc2\x6a\x73\x8d\x29\x3d\xf1\xd9\x78\x91\x83\xdf" "\xda\xf3\x24\x90\x48\x78\x13\x8e\xf5\x14\x7a\x7e\x9b\x3a\xb3\xb5\x6b\xd6\x91\xb7\x3b\xa8\xb4\xe9" "\xcd\xc1\xb5\x91\x0a\x43\x13\xfe\xc0\x5c\x1c\x4f\xba\x54\x80\xac\x76\xbd\x91\xef\x35\xc1\xcf\x29" "\xc8\xc8\x4c\x82\xbf\x69\xfb\x46\xa6\xb6\x3d\x68\xbd\x42\x44\x98\xcf\xa7\x0a\x4a\x87\x23\x83\xc8" "\x3e\x60\xf2\x7f\x89\xf8\xd4\x41\xea\x1b\xa5\x2d\x59\xab\x26\x45\x9d\x29\xa5\xea\x38\xb1\xdd\x0d" "\xa7\x50\x5b\xd8\x41\xbf\x46\x88\x12\x42\x5a\x93\x1c\xa9\x0d\xdc\x12\x83\xd9\x87\x12\xb4\x3d\x80" "\x46\x17\x52\x33\xc9\x69\x94\x5a\x95\x33\x10\xc9\x0a\x2d\x65\xde\x1b\x58\x49\x84\xfd\xe1\xdf\x50" "\xdb\xe4\xe6\x1d\xd8\x48\x77\xe1\x24\xd8\x09\x8f\x3a\xdf\x02\xe9\x15\xc8\x37\x38\x51\xe5\xc6\x74" "\x61\xc6\x5f\x6d\x2e\x21\x63\xe0\x52\xa0\x41\xaf\xb8\xb1\x63\x42\x8c\xd4\x38\x51\xda\x8f\x1d\x84" "\x25\xb6\x9a\x69\x01\x08\x6d\x00\x60\x25\x29\x1c\x00\x07\x00\x0a\x81\x16\x1b\x37\x53\xfa\x1a\x50" "\xd3\x9b\x73\xd5\x36\x99\xb9\x36\x84\xf6\x22\xdd\x7f\x99\xe0\x4e\x61\x03\xc8\x18\x93\xda\x53\x8d" "\x01\xe5\x84\x9e\x3c\xb8\xae\xe2\x76\xd8\xf5\xe9\xb9\xee\x26\x34\xdd\xf0\xb1\xc5\x8a\xd8\xed\x71" "\xcb\x55\xdc\x46\x42\x41\xf4\x57\xd0\xd9\x0a\x51\x3c\xf0\x78\xfd\xd4\x59\x79\xa2\x60\x68\x70\x8f" "\xed\x2a\x50\x5b\x0b\x5b\xbb\xc4\x6b\xbb\xf2\x16\x0c\xfd\xd4\x86\xde\x8e\xa9\x36\xa3\x64\x5b\x93" "\x0b\x50\x5e\x3e\x9f\x7e\x69\x90\xe3\x76\x68\x44\x2e\x42\x8a\xbe\xe0\x59\xfb\xad\x03\x8c\xe5\x47" "\x81\xce\x0e\x52\x0c\x47\xd2\xba\x0b\x78\x7a\xf2\xd6\xeb\xd7\x7a\xf6\x6b\xd6\x0d\x0d\x6e\x5a\xdb" "\x88\x96\xd2\x7c\x24\x27\x3c\xb1\x15\x2a\xfb\xeb\x38\x1e\x23\xc7\xe1\xf2\x4a\x06\xde\xda\xef\x67" "\x96\x87\xb0\xdc\x13\xa8\x77\x63\x51\xca\xd6\x57\x12\xaf\x19\x71\x40\x53\x11\x0b\x84\xe4\x97\x0f" "\x71\x71\xee\x7d\x49\x48\x3c\xe5\x26\xa5\x8c\x18\x10\x6d\x70\x98\xb7\x5b\x61\xb1\x12\x24\x66\xd2" "\xd3\x0c\x30\xd8\x6d\xb6\x90\x06\x02\x52\x91\xc0\x00\x79\x01\x53\x70\x34\x5e\xaf\xd3\xd4\x5c\x1f" "\x09\xd0\xf0\xe1\x31\xa6\x9c\x2c\x63\xb9\xb6\x0d\xeb\xe1\xb4\xe8\x4f\x53\xf2\x42\xf9\xb7\x40\x8f" "\x6b\xb7\xc5\xb6\x44\x40\x43\x11\x19\x43\x0d\x24\x00\x00\x4a\x12\x12\x06\x07\x03\x80\x2b\x26\x94" "\xa5\x59\x93\x9c\x5c\x49\x3a\xa5\x29\x4a\x14\x25\x29\x4a\x10\xaa\x4b\xac\x1d\xab\xfe\x4a\xb7\xca" "\xf7\x06\x1c\x6f\x0a\xd3\x7b\x57\xd7\x36\xec\x0c\x24\x36\xf2\x89\x5a\x07\xa0\x08\x74\x38\x90\x3f" "\x28\x4f\xc6\xa5\x57\xb3\x6f\x56\x1b\x86\xdd\x5f\x74\x8b\xb7\xf4\xbe\xab\x4c\xf0\xfb\x56\xe7\x07" "\xda\x46\x6d\xd1\x92\xb4\x1f\x56\x96\xa0\x7d\xdf\xc2\xb4\xac\x9c\xf8\x83\x1b\x1b\xac\x8d\x85\x1b" "\xd5\xb6\x4e\x4b\xb2\xc3\x0e\x6a\x8d\x36\x17\x36\xd7\xda\x3d\xf9\x08\xc7\xda\xc6\xff\x00\x18\x48" "\x29\xfd\xb4\xa7\xc8\x13\x50\x53\xa3\xad\xd2\x8f\xb4\xfb\xe5\x6b\x99\x75\x31\x99\xb6\xde\xd2\x6c" "\xb3\xde\x91\x94\xfd\x1d\x0e\xad\x25\x2e\x77\x7e\x1c\x38\x86\xf2\x4f\x1d\xbd\xd9\xc7\x98\x7d\x42" "\xfa\x42\x14\xef\xeb\x03\x01\xc4\x97\x69\xbc\xcc\x00\x2e\x35\x24\xb3\x30\x7a\xf9\xda\x0e\x62\xde" "\x6b\x8d\x32\x36\xd1\x4a\x52\x91\x7c\xde\x94\xa5\x28\x42\xc6\x9d\x6d\xb7\x5d\x1a\x0c\x5c\xad\xf1" "\xa5\xb6\x0e\x42\x1f\x69\x2e\x24\x1f\x8e\x14\x0d\x7d\x42\x81\x06\xdb\x1c\x45\xb7\x42\x62\x2b\x09" "\x24\x86\xd9\x6c\x21\x00\x9f\x3e\x07\x15\xcf\x4a\x13\x73\xbb\x97\x92\xf9\x74\x4a\x52\x94\x25\x4a" "\x52\x94\x21\x29\x4a\x50\x84\xa5\x29\x42\x12\xab\xe7\xae\x6e\x95\xce\x9e\x97\x2f\x7a\xf6\xf2\xdd" "\xfe\x8b\x94\xe1\x76\xfd\x05\x94\xff\x00\xaa\x3c\xa3\xcc\x94\x01\xfd\x9a\x89\xf7\xc7\xe1\x51\xcf" "\x92\x8f\x6d\x83\x57\x14\x98\xd1\xe6\x47\x76\x1c\xc8\xed\xbe\xc3\xe8\x53\x6e\xb4\xe2\x02\x90\xe2" "\x14\x30\x52\xa0\x78\x20\x82\x41\x06\xa4\x1b\x2b\x2e\x15\xc4\xf3\x78\x52\xa0\xd9\xd9\x6c\xc6\x8e" "\x6e\xce\x6e\xe3\xbe\xe0\xec\x7d\x2e\x0c\x47\xe8\x57\xa9\xa9\x9b\x85\x6f\x1b\x45\xad\xa4\xb4\xab" "\xcd\x92\x0a\x0d\xae\x5b\x8e\x92\xed\xc2\x3a\x0a\x82\x90\xac\xf0\x56\xda\x3c\x3e\x41\x25\x49\x04" "\x9f\xba\x49\x97\xb5\x00\xba\x95\xe8\xd3\x51\xed\xed\xd5\x7b\xb5\xd3\xc7\xd6\x4c\xb0\xc2\xd5\x22" "\x45\xb2\xda\xeb\x89\x99\x6e\x51\xce\x5c\x8a\xa4\x1e\xf5\x37\x82\x72\x80\x7b\x93\x9e\x32\x9e\x13" "\xee\xfa\x32\xea\xda\x7e\xbd\x4b\x5b\x5d\xb9\xd3\x21\xb7\x77\xb7\x46\x09\x85\x77\x99\x3d\x2d\xbd" "\x71\x21\x41\x29\x65\x48\x5e\x0a\xde\x00\xfd\xe0\x49\x50\x4e\x48\xce\x49\x92\x2f\x98\x57\x3c\x55" "\x86\xa4\xeb\x92\xf1\x31\x3e\x1b\x20\xc1\x39\xc4\x87\xa3\xa1\xbb\xfa\x8d\xba\x5f\x3b\x0e\xa4\xb6" "\xed\xd2\x61\x52\x94\xa5\x59\x3a\x52\x94\xa1\x09\x4a\x52\x84\x25\x29\x4a\x10\x94\xa5\x28\x42\x52" "\x94\xa1\x09\x4a\x52\x84\x25\x68\x5d\xf6\xe8\xe3\x6b\xf7\xad\x6e\xde\x9a\x68\xe9\xad\x4a\xe1\xee" "\x37\x58\x0c\x82\x1f\x57\xc5\xf6\x72\x12\xe1\xfd\xa0\x52\xbf\x2f\x78\x81\x8a\x52\x8b\xd9\x74\x69" "\x75\x69\xea\x2c\xc0\x9a\x90\x8a\x61\xbc\x6e\x3f\xe1\x1a\x11\xe8\x41\x0b\x5a\x68\x2b\x17\x56\x3d" "\x31\xde\x94\xd5\xee\x14\xdd\xd5\xd0\xaf\x04\xb6\xe2\x6d\xb2\x55\x22\x74\x44\xa7\x84\x2d\xa6\x9d" "\xc3\x9d\xc0\x60\x16\xc7\x72\x48\x00\x77\x27\x00\xd6\xfe\xb2\x75\x07\xb5\x57\x77\x51\x0e\x6e\xa0" "\x77\x4f\x4e\x50\xf7\xa1\xea\x18\x4f\x5a\x9d\x4a\xbf\x2f\xf3\x84\xa1\x2a\x3f\xa4\x9a\x52\x9b\x55" "\x7f\x97\x96\x81\x8d\x25\xa2\x54\x67\x98\x19\x15\x99\x13\x0c\x72\xf3\xdb\x77\x0c\xdb\x7f\x56\x86" "\xdf\x75\xb0\x62\x4d\x87\x70\x8e\x99\x70\x25\xb3\x25\x85\xf2\x97\x59\x70\x2d\x0a\xfd\xc4\x70\x6b" "\x9a\x94\xa5\x59\x9c\x66\x08\x71\x1c\xc1\xb1\x4a\x52\x94\x2f\xcd\x29\x4a\x50\x84\xa5\x29\x42\x17" "\xff\xd9"; minidlna-1.1.5+dfsg/image_utils.c000066400000000000000000000510711261774340000167060ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ /* These functions are mostly based on code from other projects. * There are function to effiently resize a JPEG image, and some utility functions. * They are here to allow loading and saving JPEG data directly to or from memory with libjpeg. * The standard functions only allow you to read from or write to a file. * * The reading code comes from the JpgAlleg library, at http://wiki.allegro.cc/index.php?title=Libjpeg * The writing code was posted on a Google group from openjpeg, at http://groups.google.com/group/openjpeg/browse_thread/thread/331e6cf60f70797f * The resize functions come from the resize_image project, at http://www.golac.fr/Image-Resizer */ #include "config.h" #include #include #include #include #include #include #ifdef HAVE_MACHINE_ENDIAN_H #include #else #include #endif #include "upnpreplyparse.h" #include "image_utils.h" #include "log.h" #if __BYTE_ORDER == __LITTLE_ENDIAN # define SWAP16(w) ( (((w) >> 8) & 0x00ff) | (((w) << 8) & 0xff00) ) #else # define SWAP16(w) (w) #endif #define JPEG_QUALITY 96 #define COL(red, green, blue) (((red) << 24) | ((green) << 16) | ((blue) << 8) | 0xFF) #define COL_FULL(red, green, blue, alpha) (((red) << 24) | ((green) << 16) | ((blue) << 8) | (alpha)) #define COL_RED(col) (col >> 24) #define COL_GREEN(col) ((col >> 16) & 0xFF) #define COL_BLUE(col) ((col >> 8) & 0xFF) #define COL_ALPHA(col) (col & 0xFF) #define BLACK 0x000000FF struct my_dst_mgr { struct jpeg_destination_mgr jdst; JOCTET *buf; JOCTET *off; size_t sz; size_t used; }; /* Destination manager to store data in a buffer */ static void my_dst_mgr_init(j_compress_ptr cinfo) { struct my_dst_mgr *dst = (void *)cinfo->dest; dst->used = 0; dst->sz = cinfo->image_width * cinfo->image_height * cinfo->input_components; dst->buf = malloc(dst->sz * sizeof *dst->buf); dst->off = dst->buf; dst->jdst.next_output_byte = dst->off; dst->jdst.free_in_buffer = dst->sz; return; } static boolean my_dst_mgr_empty(j_compress_ptr cinfo) { struct my_dst_mgr *dst = (void *)cinfo->dest; dst->sz *= 2; dst->used = dst->off - dst->buf; dst->buf = realloc(dst->buf, dst->sz * sizeof *dst->buf); dst->off = dst->buf + dst->used; dst->jdst.next_output_byte = dst->off; dst->jdst.free_in_buffer = dst->sz - dst->used; return TRUE; } static void my_dst_mgr_term(j_compress_ptr cinfo) { struct my_dst_mgr *dst = (void *)cinfo->dest; dst->used += dst->sz - dst->jdst.free_in_buffer; dst->off = dst->buf + dst->used; return; } static void jpeg_memory_dest(j_compress_ptr cinfo, struct my_dst_mgr *dst) { dst->jdst.init_destination = my_dst_mgr_init; dst->jdst.empty_output_buffer = my_dst_mgr_empty; dst->jdst.term_destination = my_dst_mgr_term; cinfo->dest = (void *)dst; return; } /* Source manager to read data from a buffer */ struct my_src_mgr { struct jpeg_source_mgr pub; JOCTET eoi_buffer[2]; }; static void init_source(j_decompress_ptr cinfo) { return; } static int fill_input_buffer(j_decompress_ptr cinfo) { struct my_src_mgr *src = (void *)cinfo->src; /* Create a fake EOI marker */ src->eoi_buffer[0] = (JOCTET) 0xFF; src->eoi_buffer[1] = (JOCTET) JPEG_EOI; src->pub.next_input_byte = src->eoi_buffer; src->pub.bytes_in_buffer = 2; return TRUE; } static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) { struct my_src_mgr *src = (void *)cinfo->src; if (num_bytes > 0) { while (num_bytes > (long)src->pub.bytes_in_buffer) { num_bytes -= (long)src->pub.bytes_in_buffer; fill_input_buffer(cinfo); } } src->pub.next_input_byte += num_bytes; src->pub.bytes_in_buffer -= num_bytes; } static void term_source(j_decompress_ptr cinfo) { return; } void jpeg_memory_src(j_decompress_ptr cinfo, const unsigned char * buffer, size_t bufsize) { struct my_src_mgr *src; if (!cinfo->src) cinfo->src = (*cinfo->mem->alloc_small)((void *)cinfo, JPOOL_PERMANENT, sizeof(struct my_src_mgr));; src = (void *)cinfo->src; src->pub.init_source = init_source; src->pub.fill_input_buffer = fill_input_buffer; src->pub.skip_input_data = skip_input_data; src->pub.resync_to_restart = jpeg_resync_to_restart; src->pub.term_source = term_source; src->pub.next_input_byte = buffer; src->pub.bytes_in_buffer = bufsize; } jmp_buf setjmp_buffer; /* Don't exit on error like libjpeg likes to do */ static void libjpeg_error_handler(j_common_ptr cinfo) { cinfo->err->output_message(cinfo); longjmp(setjmp_buffer, 1); return; } void image_free(image_s *pimage) { free(pimage->buf); free(pimage); } pix get_pix(image_s *pimage, int32_t x, int32_t y) { if (x < 0) x = 0; else if (x >= pimage->width) x = pimage->width - 1; if (y < 0) y = 0; else if (y >= pimage->height) y = pimage->height - 1; return(pimage->buf[(y * pimage->width) + x]); } void put_pix_alpha_replace(image_s *pimage, int32_t x, int32_t y, pix col) { if((x >= 0) && (y >= 0) && (x < pimage->width) && (y < pimage->height)) pimage->buf[(y * pimage->width) + x] = col; } int image_get_jpeg_resolution(const char * path, int * width, int * height) { FILE *img; unsigned char buf[8]; uint16_t offset, h, w; int ret = 1; size_t nread; long size; img = fopen(path, "r"); if( !img ) return -1; fseek(img, 0, SEEK_END); size = ftell(img); rewind(img); nread = fread(&buf, 2, 1, img); if( (nread < 1) || (buf[0] != 0xFF) || (buf[1] != 0xD8) ) { fclose(img); return -1; } memset(&buf, 0, sizeof(buf)); while( ftell(img) < size ) { while( nread > 0 && buf[0] != 0xFF && !feof(img) ) nread = fread(&buf, 1, 1, img); while( nread > 0 && buf[0] == 0xFF && !feof(img) ) nread = fread(&buf, 1, 1, img); if( (buf[0] >= 0xc0) && (buf[0] <= 0xc3) ) { nread = fread(&buf, 7, 1, img); *width = 0; *height = 0; if( nread < 1 ) break; memcpy(&h, buf+3, 2); *height = SWAP16(h); memcpy(&w, buf+5, 2); *width = SWAP16(w); ret = 0; break; } else { offset = 0; nread = fread(&buf, 2, 1, img); if( nread < 1 ) break; memcpy(&offset, buf, 2); offset = SWAP16(offset) - 2; if( fseek(img, offset, SEEK_CUR) == -1 ) break; } } fclose(img); return ret; } int image_get_jpeg_date_xmp(const char * path, char ** date) { FILE *img; unsigned char buf[8]; char *data = NULL, *newdata; uint16_t offset; struct NameValueParserData xml; char * exif; int ret = 1; size_t nread; img = fopen(path, "r"); if( !img ) return(-1); nread = fread(&buf, 2, 1, img); if( (nread < 1) || (buf[0] != 0xFF) || (buf[1] != 0xD8) ) { fclose(img); return(-1); } memset(&buf, 0, sizeof(buf)); while( !feof(img) ) { while( nread > 0 && buf[0] != 0xFF && !feof(img) ) nread = fread(&buf, 1, 1, img); while( nread > 0 && buf[0] == 0xFF && !feof(img) ) nread = fread(&buf, 1, 1, img); if( feof(img) ) break; if( buf[0] == 0xE1 ) // APP1 marker { offset = 0; nread = fread(&buf, 2, 1, img); if( nread < 1 ) break; memcpy(&offset, buf, 2); offset = SWAP16(offset) - 2; if( offset < 30 ) { fseek(img, offset, SEEK_CUR); continue; } newdata = realloc(data, 30); if( !newdata ) break; data = newdata; nread = fread(data, 29, 1, img); if( nread < 1 ) break; offset -= 29; if( strcmp(data, "http://ns.adobe.com/xap/1.0/") != 0 ) { fseek(img, offset, SEEK_CUR); continue; } newdata = realloc(data, offset+1); if( !newdata ) break; data = newdata; nread = fread(data, offset, 1, img); if( nread < 1 ) break; ParseNameValue(data, offset, &xml, 0); exif = GetValueFromNameValueList(&xml, "DateTimeOriginal"); if( !exif ) { ClearNameValueList(&xml); break; } *date = realloc(*date, strlen(exif)+1); strcpy(*date, exif); ClearNameValueList(&xml); ret = 0; break; } else { offset = 0; nread = fread(&buf, 2, 1, img); if( nread < 1 ) break; memcpy(&offset, buf, 2); offset = SWAP16(offset) - 2; fseek(img, offset, SEEK_CUR); } } fclose(img); free(data); return ret; } image_s * image_new(int32_t width, int32_t height) { image_s *vimage; if((vimage = (image_s *)malloc(sizeof(image_s))) == NULL) { DPRINTF(E_WARN, L_METADATA, "malloc failed\n"); return NULL; } vimage->width = width; vimage->height = height; if((vimage->buf = (pix *)malloc(width * height * sizeof(pix))) == NULL) { DPRINTF(E_WARN, L_METADATA, "malloc failed\n"); free(vimage); return NULL; } return(vimage); } image_s * image_new_from_jpeg(const char *path, int is_file, const uint8_t *buf, int size, int scale, int rotate) { image_s *vimage; FILE *file = NULL; struct jpeg_decompress_struct cinfo; unsigned char *line[16], *ptr; int x, y, i, w, h, ofs; int maxbuf; struct jpeg_error_mgr pub; cinfo.err = jpeg_std_error(&pub); pub.error_exit = libjpeg_error_handler; jpeg_create_decompress(&cinfo); if( is_file ) { if( (file = fopen(path, "r")) == NULL ) { return NULL; } jpeg_stdio_src(&cinfo, file); } else { jpeg_memory_src(&cinfo, buf, size); } if( setjmp(setjmp_buffer) ) { jpeg_destroy_decompress(&cinfo); if( is_file && file ) fclose(file); return NULL; } jpeg_read_header(&cinfo, TRUE); cinfo.scale_denom = scale; cinfo.do_fancy_upsampling = FALSE; cinfo.do_block_smoothing = FALSE; cinfo.dct_method = JDCT_IFAST; jpeg_start_decompress(&cinfo); w = cinfo.output_width; h = cinfo.output_height; vimage = (rotate & (ROTATE_90|ROTATE_270)) ? image_new(h, w) : image_new(w, h); if(!vimage) { jpeg_destroy_decompress(&cinfo); if( is_file ) fclose(file); return NULL; } if( setjmp(setjmp_buffer) ) { jpeg_destroy_decompress(&cinfo); if( is_file && file ) fclose(file); if( vimage ) { free(vimage->buf); free(vimage); } return NULL; } if(cinfo.rec_outbuf_height > 16) { DPRINTF(E_WARN, L_METADATA, "ERROR image_from_jpeg : (image_from_jpeg.c) JPEG uses line buffers > 16. Cannot load.\n"); jpeg_destroy_decompress(&cinfo); image_free(vimage); if( is_file ) fclose(file); return NULL; } maxbuf = vimage->width * vimage->height; if(cinfo.output_components == 3) { int rx, ry; ofs = 0; if((ptr = malloc(w * 3 * cinfo.rec_outbuf_height + 16)) == NULL) { DPRINTF(E_WARN, L_METADATA, "malloc failed\n"); jpeg_destroy_decompress(&cinfo); image_free(vimage); if( is_file ) fclose(file); return NULL; } for(y = 0; y < h; y += cinfo.rec_outbuf_height) { ry = (rotate & (ROTATE_90|ROTATE_180)) ? (y - h + 1) * -1 : y; for(i = 0; i < cinfo.rec_outbuf_height; i++) { line[i] = ptr + (w * 3 * i); } jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height); for(x = 0; x < w * cinfo.rec_outbuf_height; x++) { rx = (rotate & (ROTATE_180|ROTATE_270)) ? (x - w + 1) * -1 : x; ofs = (rotate & (ROTATE_90|ROTATE_270)) ? ry + (rx * h) : rx + (ry * w); if( ofs < maxbuf ) vimage->buf[ofs] = COL(ptr[x + x + x], ptr[x + x + x + 1], ptr[x + x + x + 2]); } } free(ptr); } else if(cinfo.output_components == 1) { int rx, ry; ofs = 0; for(i = 0; i < cinfo.rec_outbuf_height; i++) { if((line[i] = malloc(w)) == NULL) { int t = 0; for(t = 0; t < i; t++) free(line[t]); jpeg_destroy_decompress(&cinfo); image_free(vimage); if( is_file ) fclose(file); return NULL; } } for(y = 0; y < h; y += cinfo.rec_outbuf_height) { ry = (rotate & (ROTATE_90|ROTATE_180)) ? (y - h + 1) * -1 : y; jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height); for(i = 0; i < cinfo.rec_outbuf_height; i++) { for(x = 0; x < w; x++) { rx = (rotate & (ROTATE_180|ROTATE_270)) ? (x - w + 1) * -1 : x; ofs = (rotate & (ROTATE_90|ROTATE_270)) ? ry + (rx * h) : rx + (ry * w); if( ofs < maxbuf ) vimage->buf[ofs] = COL(line[i][x], line[i][x], line[i][x]); } } } for(i = 0; i < cinfo.rec_outbuf_height; i++) { free(line[i]); } } jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); if( is_file ) fclose(file); return vimage; } void image_upsize(image_s * pdest, image_s * psrc, int32_t width, int32_t height) { int32_t vx, vy; #if !defined __i386__ && !defined __x86_64__ int32_t rx, ry; pix vcol; if((pdest == NULL) || (psrc == NULL)) return; for(vy = 0; vy < height; vy++) { for(vx = 0; vx < width; vx++) { rx = ((vx * psrc->width) / width); ry = ((vy * psrc->height) / height); vcol = get_pix(psrc, rx, ry); #else pix vcol,vcol1,vcol2,vcol3,vcol4; float rx,ry; float width_scale, height_scale; float x_dist, y_dist; width_scale = (float)psrc->width / (float)width; height_scale = (float)psrc->height / (float)height; for(vy = 0;vy < height; vy++) { for(vx = 0;vx < width; vx++) { rx = vx * width_scale; ry = vy * height_scale; vcol1 = get_pix(psrc, (int32_t)rx, (int32_t)ry); vcol2 = get_pix(psrc, ((int32_t)rx)+1, (int32_t)ry); vcol3 = get_pix(psrc, (int32_t)rx, ((int32_t)ry)+1); vcol4 = get_pix(psrc, ((int32_t)rx)+1, ((int32_t)ry)+1); x_dist = rx - ((float)((int32_t)rx)); y_dist = ry - ((float)((int32_t)ry)); vcol = COL_FULL( (uint8_t)((COL_RED(vcol1)*(1.0-x_dist) + COL_RED(vcol2)*(x_dist))*(1.0-y_dist) + (COL_RED(vcol3)*(1.0-x_dist) + COL_RED(vcol4)*(x_dist))*(y_dist)), (uint8_t)((COL_GREEN(vcol1)*(1.0-x_dist) + COL_GREEN(vcol2)*(x_dist))*(1.0-y_dist) + (COL_GREEN(vcol3)*(1.0-x_dist) + COL_GREEN(vcol4)*(x_dist))*(y_dist)), (uint8_t)((COL_BLUE(vcol1)*(1.0-x_dist) + COL_BLUE(vcol2)*(x_dist))*(1.0-y_dist) + (COL_BLUE(vcol3)*(1.0-x_dist) + COL_BLUE(vcol4)*(x_dist))*(y_dist)), (uint8_t)((COL_ALPHA(vcol1)*(1.0-x_dist) + COL_ALPHA(vcol2)*(x_dist))*(1.0-y_dist) + (COL_ALPHA(vcol3)*(1.0-x_dist) + COL_ALPHA(vcol4)*(x_dist))*(y_dist)) ); #endif put_pix_alpha_replace(pdest, vx, vy, vcol); } } } void image_downsize(image_s * pdest, image_s * psrc, int32_t width, int32_t height) { int32_t vx, vy; pix vcol; int32_t i, j; #if !defined __i386__ && !defined __x86_64__ int32_t rx, ry, rx_next, ry_next; int red, green, blue, alpha; int factor; if((pdest == NULL) || (psrc == NULL)) return; for(vy = 0; vy < height; vy++) { for(vx = 0; vx < width; vx++) { rx = ((vx * psrc->width) / width); ry = ((vy * psrc->height) / height); red = green = blue = alpha = 0; rx_next = rx + (psrc->width / width); ry_next = ry + (psrc->width / width); factor = 0; for( j = rx; j < rx_next; j++) { for( i = ry; i < ry_next; i++) { factor += 1; vcol = get_pix(psrc, j, i); red += COL_RED(vcol); green += COL_GREEN(vcol); blue += COL_BLUE(vcol); alpha += COL_ALPHA(vcol); } } red /= factor; green /= factor; blue /= factor; alpha /= factor; /* on sature les valeurs */ red = (red > 255) ? 255 : ((red < 0) ? 0 : red ); green = (green > 255) ? 255 : ((green < 0) ? 0 : green); blue = (blue > 255) ? 255 : ((blue < 0) ? 0 : blue ); alpha = (alpha > 255) ? 255 : ((alpha < 0) ? 0 : alpha); #else float rx,ry; float width_scale, height_scale; float red, green, blue, alpha; int32_t half_square_width, half_square_height; float round_width, round_height; if( (pdest == NULL) || (psrc == NULL) ) return; width_scale = (float)psrc->width / (float)width; height_scale = (float)psrc->height / (float)height; half_square_width = (int32_t)(width_scale / 2.0); half_square_height = (int32_t)(height_scale / 2.0); round_width = (width_scale / 2.0) - (float)half_square_width; round_height = (height_scale / 2.0) - (float)half_square_height; if(round_width > 0.0) half_square_width++; else round_width = 1.0; if(round_height > 0.0) half_square_height++; else round_height = 1.0; for(vy = 0;vy < height; vy++) { for(vx = 0;vx < width; vx++) { rx = vx * width_scale; ry = vy * height_scale; vcol = get_pix(psrc, (int32_t)rx, (int32_t)ry); red = green = blue = alpha = 0.0; for(j=0;j 255.0)? 255.0 : ((red < 0.0)? 0.0:red ); green = (green > 255.0)? 255.0 : ((green < 0.0)? 0.0:green); blue = (blue > 255.0)? 255.0 : ((blue < 0.0)? 0.0:blue ); alpha = (alpha > 255.0)? 255.0 : ((alpha < 0.0)? 0.0:alpha); #endif put_pix_alpha_replace(pdest, vx, vy, COL_FULL((uint8_t)red, (uint8_t)green, (uint8_t)blue, (uint8_t)alpha)); } } } image_s * image_resize(image_s * src_image, int32_t width, int32_t height) { image_s * dst_image; dst_image = image_new(width, height); if( !dst_image ) return NULL; if( (src_image->width < width) || (src_image->height < height) ) image_upsize(dst_image, src_image, width, height); else image_downsize(dst_image, src_image, width, height); return dst_image; } unsigned char * image_save_to_jpeg_buf(image_s * pimage, int * size) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; JSAMPROW row_pointer[1]; int row_stride; char *data; int i, x; struct my_dst_mgr dst; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); jpeg_memory_dest(&cinfo, &dst); cinfo.image_width = pimage->width; cinfo.image_height = pimage->height; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, JPEG_QUALITY, TRUE); jpeg_start_compress(&cinfo, TRUE); row_stride = cinfo.image_width * 3; if((data = malloc(row_stride)) == NULL) { DPRINTF(E_WARN, L_METADATA, "malloc failed\n"); free(dst.buf); jpeg_destroy_compress(&cinfo); return NULL; } i = 0; while(cinfo.next_scanline < cinfo.image_height) { for(x = 0; x < pimage->width; x++) { data[x * 3] = COL_RED(pimage->buf[i]); data[x * 3 + 1] = COL_GREEN(pimage->buf[i]); data[x * 3 + 2] = COL_BLUE(pimage->buf[i]); i++; } row_pointer[0] = (unsigned char *)data; jpeg_write_scanlines(&cinfo, row_pointer, 1); } jpeg_finish_compress(&cinfo); *size = dst.used; free(data); jpeg_destroy_compress(&cinfo); return dst.buf; } char * image_save_to_jpeg_file(image_s * pimage, char * path) { int nwritten, size = 0; unsigned char * buf; FILE * dst_file; buf = image_save_to_jpeg_buf(pimage, &size); if( !buf ) return NULL; dst_file = fopen(path, "w"); if( !dst_file ) { free(buf); return NULL; } nwritten = fwrite(buf, 1, size, dst_file); fclose(dst_file); free(buf); return (nwritten == size) ? path : NULL; } minidlna-1.1.5+dfsg/image_utils.h000066400000000000000000000030051261774340000167050ustar00rootroot00000000000000/* Image manipulation functions * * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Justin Maggard * * MiniDLNA media server * Copyright (C) 2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include #define ROTATE_NONE 0x0 #define ROTATE_90 0x1 #define ROTATE_180 0x2 #define ROTATE_270 0x4 typedef uint32_t pix; typedef struct { int32_t width; int32_t height; pix *buf; } image_s; void image_free(image_s *pimage); int image_get_jpeg_date_xmp(const char * path, char ** date); int image_get_jpeg_resolution(const char * path, int * width, int * height); image_s * image_new_from_jpeg(const char *path, int is_file, const uint8_t *ptr, int size, int scale, int resize); image_s * image_resize(image_s * src_image, int32_t width, int32_t height); unsigned char * image_save_to_jpeg_buf(image_s * pimage, int * size); char * image_save_to_jpeg_file(image_s * pimage, char * path); minidlna-1.1.5+dfsg/inotify.c000066400000000000000000000452621261774340000160720ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2008-2010 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include "config.h" #ifdef HAVE_INOTIFY #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_INOTIFY_H #include #else #include "linux/inotify.h" #include "linux/inotify-syscalls.h" #endif #include "libav.h" #include "upnpglobalvars.h" #include "inotify.h" #include "utils.h" #include "sql.h" #include "scanner.h" #include "metadata.h" #include "albumart.h" #include "playlist.h" #include "log.h" #define EVENT_SIZE ( sizeof (struct inotify_event) ) #define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) ) #define DESIRED_WATCH_LIMIT 65536 #define PATH_BUF_SIZE PATH_MAX struct watch { int wd; /* watch descriptor */ char *path; /* watched path */ struct watch *next; }; static struct watch *watches; static struct watch *lastwatch = NULL; static time_t next_pl_fill = 0; char *get_path_from_wd(int wd) { struct watch *w = watches; while( w != NULL ) { if( w->wd == wd ) return w->path; w = w->next; } return NULL; } int add_watch(int fd, const char * path) { struct watch *nw; int wd; wd = inotify_add_watch(fd, path, IN_CREATE|IN_CLOSE_WRITE|IN_DELETE|IN_MOVE); if( wd < 0 ) { DPRINTF(E_ERROR, L_INOTIFY, "inotify_add_watch(%s) [%s]\n", path, strerror(errno)); return -1; } nw = malloc(sizeof(struct watch)); if( nw == NULL ) { DPRINTF(E_ERROR, L_INOTIFY, "malloc() error\n"); return -1; } nw->wd = wd; nw->next = NULL; nw->path = strdup(path); if( watches == NULL ) { watches = nw; } if( lastwatch != NULL ) { lastwatch->next = nw; } lastwatch = nw; return wd; } int remove_watch(int fd, const char * path) { struct watch *w; for( w = watches; w; w = w->next ) { if( strcmp(path, w->path) == 0 ) return(inotify_rm_watch(fd, w->wd)); } return 1; } unsigned int next_highest(unsigned int num) { num |= num >> 1; num |= num >> 2; num |= num >> 4; num |= num >> 8; num |= num >> 16; return(++num); } int inotify_create_watches(int fd) { FILE * max_watches; unsigned int num_watches = 0, watch_limit; char **result; int i, rows = 0; struct media_dir_s * media_path; for( media_path = media_dirs; media_path != NULL; media_path = media_path->next ) { DPRINTF(E_DEBUG, L_INOTIFY, "Add watch to %s\n", media_path->path); add_watch(fd, media_path->path); num_watches++; } sql_get_table(db, "SELECT PATH from DETAILS where MIME is NULL and PATH is not NULL", &result, &rows, NULL); for( i=1; i <= rows; i++ ) { DPRINTF(E_DEBUG, L_INOTIFY, "Add watch to %s\n", result[i]); add_watch(fd, result[i]); num_watches++; } sqlite3_free_table(result); max_watches = fopen("/proc/sys/fs/inotify/max_user_watches", "r"); if( max_watches ) { if( fscanf(max_watches, "%10u", &watch_limit) < 1 ) watch_limit = 8192; fclose(max_watches); if( (watch_limit < DESIRED_WATCH_LIMIT) || (watch_limit < (num_watches*4/3)) ) { max_watches = fopen("/proc/sys/fs/inotify/max_user_watches", "w"); if( max_watches ) { if( DESIRED_WATCH_LIMIT >= (num_watches*3/4) ) { fprintf(max_watches, "%u", DESIRED_WATCH_LIMIT); } else if( next_highest(num_watches) >= (num_watches*3/4) ) { fprintf(max_watches, "%u", next_highest(num_watches)); } else { fprintf(max_watches, "%u", next_highest(next_highest(num_watches))); } fclose(max_watches); } else { DPRINTF(E_WARN, L_INOTIFY, "WARNING: Inotify max_user_watches [%u] is low or close to the number of used watches [%u] " "and I do not have permission to increase this limit. Please do so manually by " "writing a higher value into /proc/sys/fs/inotify/max_user_watches.\n", watch_limit, num_watches); } } } else { DPRINTF(E_WARN, L_INOTIFY, "WARNING: Could not read inotify max_user_watches! " "Hopefully it is enough to cover %u current directories plus any new ones added.\n", num_watches); } return rows; } int inotify_remove_watches(int fd) { struct watch *w = watches; struct watch *last_w; int rm_watches = 0; while( w ) { last_w = w; inotify_rm_watch(fd, w->wd); free(w->path); rm_watches++; w = w->next; free(last_w); } return rm_watches; } int add_dir_watch(int fd, char * path, char * filename) { DIR *ds; struct dirent *e; char *dir; char buf[PATH_MAX]; int wd; int i = 0; if( filename ) { snprintf(buf, sizeof(buf), "%s/%s", path, filename); dir = buf; } else dir = path; wd = add_watch(fd, dir); if( wd == -1 ) { DPRINTF(E_ERROR, L_INOTIFY, "add_watch() [%s]\n", strerror(errno)); } else { DPRINTF(E_INFO, L_INOTIFY, "Added watch to %s [%d]\n", dir, wd); } ds = opendir(dir); if( ds != NULL ) { while( (e = readdir(ds)) ) { if( strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0 ) continue; if( (e->d_type == DT_DIR) || (e->d_type == DT_UNKNOWN && resolve_unknown_type(dir, NO_MEDIA) == TYPE_DIR) ) i += add_dir_watch(fd, dir, e->d_name); } } else { DPRINTF(E_ERROR, L_INOTIFY, "Opendir error! [%s]\n", strerror(errno)); } closedir(ds); i++; return(i); } int inotify_insert_file(char * name, const char * path) { int len; char * last_dir; char * path_buf; char * base_name; char * base_copy; char * parent_buf = NULL; char * id = NULL; int depth = 1; int ts; media_types types = ALL_MEDIA; struct media_dir_s * media_path = media_dirs; struct stat st; /* Is it cover art for another file? */ if( is_image(path) ) update_if_album_art(path); else if( is_caption(path) ) check_for_captions(path, 0); /* Check if we're supposed to be scanning for this file type in this directory */ while( media_path ) { if( strncmp(path, media_path->path, strlen(media_path->path)) == 0 ) { types = media_path->types; break; } media_path = media_path->next; } switch( types ) { case ALL_MEDIA: if( !is_image(path) && !is_audio(path) && !is_video(path) && !is_playlist(path) ) return -1; break; case TYPE_AUDIO: if( !is_audio(path) && !is_playlist(path) ) return -1; break; case TYPE_AUDIO|TYPE_VIDEO: if( !is_audio(path) && !is_video(path) && !is_playlist(path) ) return -1; break; case TYPE_AUDIO|TYPE_IMAGES: if( !is_image(path) && !is_audio(path) && !is_playlist(path) ) return -1; break; case TYPE_VIDEO: if( !is_video(path) ) return -1; break; case TYPE_VIDEO|TYPE_IMAGES: if( !is_image(path) && !is_video(path) ) return -1; break; case TYPE_IMAGES: if( !is_image(path) ) return -1; break; default: return -1; break; } /* If it's already in the database and hasn't been modified, skip it. */ if( stat(path, &st) != 0 ) return -1; ts = sql_get_int_field(db, "SELECT TIMESTAMP from DETAILS where PATH = '%q'", path); if( !ts && is_playlist(path) && (sql_get_int_field(db, "SELECT ID from PLAYLISTS where PATH = '%q'", path) > 0) ) { DPRINTF(E_DEBUG, L_INOTIFY, "Re-reading modified playlist (%s).\n", path); inotify_remove_file(path); next_pl_fill = 1; } else if( ts < st.st_mtime ) { if( ts > 0 ) DPRINTF(E_DEBUG, L_INOTIFY, "%s is newer than the last db entry.\n", path); inotify_remove_file(path); } /* Find the parentID. If it's not found, create all necessary parents. */ len = strlen(path)+1; if( !(path_buf = malloc(len)) || !(last_dir = malloc(len)) || !(base_name = malloc(len)) ) return -1; base_copy = base_name; while( depth ) { depth = 0; strcpy(path_buf, path); parent_buf = dirname(path_buf); do { //DEBUG DPRINTF(E_DEBUG, L_INOTIFY, "Checking %s\n", parent_buf); id = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" " where d.PATH = '%q' and REF_ID is NULL", parent_buf); if( id ) { if( !depth ) break; DPRINTF(E_DEBUG, L_INOTIFY, "Found first known parentID: %s [%s]\n", id, parent_buf); /* Insert newly-found directory */ strcpy(base_name, last_dir); base_copy = basename(base_name); insert_directory(base_copy, last_dir, BROWSEDIR_ID, id+2, get_next_available_id("OBJECTS", id)); sqlite3_free(id); break; } depth++; strcpy(last_dir, parent_buf); parent_buf = dirname(parent_buf); } while( strcmp(parent_buf, "/") != 0 ); if( strcmp(parent_buf, "/") == 0 ) { id = sqlite3_mprintf("%s", BROWSEDIR_ID); depth = 0; break; } strcpy(path_buf, path); } free(last_dir); free(path_buf); free(base_name); if( !depth ) { //DEBUG DPRINTF(E_DEBUG, L_INOTIFY, "Inserting %s\n", name); insert_file(name, path, id+2, get_next_available_id("OBJECTS", id), types); sqlite3_free(id); if( (is_audio(path) || is_playlist(path)) && next_pl_fill != 1 ) { next_pl_fill = time(NULL) + 120; // Schedule a playlist scan for 2 minutes from now. //DEBUG DPRINTF(E_WARN, L_INOTIFY, "Playlist scan scheduled for %s", ctime(&next_pl_fill)); } } return depth; } int inotify_insert_directory(int fd, char *name, const char * path) { DIR * ds; struct dirent * e; char *id, *parent_buf, *esc_name; char path_buf[PATH_MAX]; int wd; enum file_types type = TYPE_UNKNOWN; media_types dir_types = ALL_MEDIA; struct media_dir_s* media_path; struct stat st; if( access(path, R_OK|X_OK) != 0 ) { DPRINTF(E_WARN, L_INOTIFY, "Could not access %s [%s]\n", path, strerror(errno)); return -1; } if( sql_get_int_field(db, "SELECT ID from DETAILS where PATH = '%q'", path) > 0 ) { DPRINTF(E_DEBUG, L_INOTIFY, "%s already exists\n", path); return 0; } parent_buf = strdup(path); id = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" " where d.PATH = '%q' and REF_ID is NULL", dirname(parent_buf)); if( !id ) id = sqlite3_mprintf("%s", BROWSEDIR_ID); insert_directory(name, path, BROWSEDIR_ID, id+2, get_next_available_id("OBJECTS", id)); sqlite3_free(id); free(parent_buf); wd = add_watch(fd, path); if( wd == -1 ) { DPRINTF(E_ERROR, L_INOTIFY, "add_watch() failed\n"); } else { DPRINTF(E_INFO, L_INOTIFY, "Added watch to %s [%d]\n", path, wd); } media_path = media_dirs; while( media_path ) { if( strncmp(path, media_path->path, strlen(media_path->path)) == 0 ) { dir_types = media_path->types; break; } media_path = media_path->next; } ds = opendir(path); if( !ds ) { DPRINTF(E_ERROR, L_INOTIFY, "opendir failed! [%s]\n", strerror(errno)); return -1; } while( (e = readdir(ds)) ) { if( e->d_name[0] == '.' ) continue; esc_name = escape_tag(e->d_name, 1); snprintf(path_buf, sizeof(path_buf), "%s/%s", path, e->d_name); switch( e->d_type ) { case DT_DIR: case DT_REG: case DT_LNK: case DT_UNKNOWN: type = resolve_unknown_type(path_buf, dir_types); default: break; } if( type == TYPE_DIR ) { inotify_insert_directory(fd, esc_name, path_buf); } else if( type == TYPE_FILE ) { if( (stat(path_buf, &st) == 0) && (st.st_blocks<<9 >= st.st_size) ) { inotify_insert_file(esc_name, path_buf); } } free(esc_name); } closedir(ds); return 0; } int inotify_remove_file(const char * path) { char sql[128]; char art_cache[PATH_MAX]; char *id; char *ptr; char **result; int64_t detailID; int rows, playlist; if( is_caption(path) ) { return sql_exec(db, "DELETE from CAPTIONS where PATH = '%q'", path); } /* Invalidate the scanner cache so we don't insert files into non-existent containers */ valid_cache = 0; playlist = is_playlist(path); id = sql_get_text_field(db, "SELECT ID from %s where PATH = '%q'", playlist?"PLAYLISTS":"DETAILS", path); if( !id ) return 1; detailID = strtoll(id, NULL, 10); sqlite3_free(id); if( playlist ) { sql_exec(db, "DELETE from PLAYLISTS where ID = %lld", detailID); sql_exec(db, "DELETE from DETAILS where ID =" " (SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s$%llX')", MUSIC_PLIST_ID, detailID); sql_exec(db, "DELETE from OBJECTS where OBJECT_ID = '%s$%llX' or PARENT_ID = '%s$%llX'", MUSIC_PLIST_ID, detailID, MUSIC_PLIST_ID, detailID); } else { /* Delete the parent containers if we are about to empty them. */ snprintf(sql, sizeof(sql), "SELECT PARENT_ID from OBJECTS where DETAIL_ID = %lld" " and PARENT_ID not like '64$%%'", (long long int)detailID); if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) ) { int i, children; for( i = 1; i <= rows; i++ ) { /* If it's a playlist item, adjust the item count of the playlist */ if( strncmp(result[i], MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) { sql_exec(db, "UPDATE PLAYLISTS set FOUND = (FOUND-1) where ID = %d", atoi(strrchr(result[i], '$') + 1)); } children = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", result[i]); if( children < 0 ) continue; if( children < 2 ) { sql_exec(db, "DELETE from OBJECTS where OBJECT_ID = '%s'", result[i]); ptr = strrchr(result[i], '$'); if( ptr ) *ptr = '\0'; if( sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", result[i]) == 0 ) { sql_exec(db, "DELETE from OBJECTS where OBJECT_ID = '%s'", result[i]); } } } sqlite3_free_table(result); } /* Now delete the actual objects */ sql_exec(db, "DELETE from DETAILS where ID = %lld", detailID); sql_exec(db, "DELETE from OBJECTS where DETAIL_ID = %lld", detailID); } snprintf(art_cache, sizeof(art_cache), "%s/art_cache%s", db_path, path); remove(art_cache); return 0; } int inotify_remove_directory(int fd, const char * path) { char * sql; char **result; int64_t detailID = 0; int rows, i, ret = 1; /* Invalidate the scanner cache so we don't insert files into non-existent containers */ valid_cache = 0; remove_watch(fd, path); sql = sqlite3_mprintf("SELECT ID from DETAILS where (PATH > '%q/' and PATH <= '%q/%c')" " or PATH = '%q'", path, path, 0xFF, path); if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) ) { if( rows ) { for( i=1; i <= rows; i++ ) { detailID = strtoll(result[i], NULL, 10); sql_exec(db, "DELETE from DETAILS where ID = %lld", detailID); sql_exec(db, "DELETE from OBJECTS where DETAIL_ID = %lld", detailID); } ret = 0; } sqlite3_free_table(result); } sqlite3_free(sql); /* Clean up any album art entries in the deleted directory */ sql_exec(db, "DELETE from ALBUM_ART where (PATH > '%q/' and PATH <= '%q/%c')", path, path, 0xFF); return ret; } void * start_inotify() { struct pollfd pollfds[1]; int timeout = 1000; char buffer[BUF_LEN]; char path_buf[PATH_MAX]; int length, i = 0; char * esc_name = NULL; struct stat st; pollfds[0].fd = inotify_init(); pollfds[0].events = POLLIN; if ( pollfds[0].fd < 0 ) DPRINTF(E_ERROR, L_INOTIFY, "inotify_init() failed!\n"); while( scanning ) { if( quitting ) goto quitting; sleep(1); } inotify_create_watches(pollfds[0].fd); if (setpriority(PRIO_PROCESS, 0, 19) == -1) DPRINTF(E_WARN, L_INOTIFY, "Failed to reduce inotify thread priority\n"); sqlite3_release_memory(1<<31); av_register_all(); while( !quitting ) { length = poll(pollfds, 1, timeout); if( !length ) { if( next_pl_fill && (time(NULL) >= next_pl_fill) ) { fill_playlists(); next_pl_fill = 0; } continue; } else if( length < 0 ) { if( (errno == EINTR) || (errno == EAGAIN) ) continue; else DPRINTF(E_ERROR, L_INOTIFY, "read failed!\n"); } else { length = read(pollfds[0].fd, buffer, BUF_LEN); buffer[BUF_LEN-1] = '\0'; } i = 0; while( i < length ) { struct inotify_event * event = (struct inotify_event *) &buffer[i]; if( event->len ) { if( *(event->name) == '.' ) { i += EVENT_SIZE + event->len; continue; } esc_name = modifyString(strdup(event->name), "&", "&amp;", 0); snprintf(path_buf, sizeof(path_buf), "%s/%s", get_path_from_wd(event->wd), event->name); if ( event->mask & IN_ISDIR && (event->mask & (IN_CREATE|IN_MOVED_TO)) ) { DPRINTF(E_DEBUG, L_INOTIFY, "The directory %s was %s.\n", path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "created")); inotify_insert_directory(pollfds[0].fd, esc_name, path_buf); } else if ( (event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO|IN_CREATE)) && (lstat(path_buf, &st) == 0) ) { if( (event->mask & (IN_MOVED_TO|IN_CREATE)) && (S_ISLNK(st.st_mode) || st.st_nlink > 1) ) { DPRINTF(E_DEBUG, L_INOTIFY, "The %s link %s was %s.\n", (S_ISLNK(st.st_mode) ? "symbolic" : "hard"), path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "created")); if( stat(path_buf, &st) == 0 && S_ISDIR(st.st_mode) ) inotify_insert_directory(pollfds[0].fd, esc_name, path_buf); else inotify_insert_file(esc_name, path_buf); } else if( event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO) && st.st_size > 0 ) { if( (event->mask & IN_MOVED_TO) || (sql_get_int_field(db, "SELECT TIMESTAMP from DETAILS where PATH = '%q'", path_buf) != st.st_mtime) ) { DPRINTF(E_DEBUG, L_INOTIFY, "The file %s was %s.\n", path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "changed")); inotify_insert_file(esc_name, path_buf); } } } else if ( event->mask & (IN_DELETE|IN_MOVED_FROM) ) { DPRINTF(E_DEBUG, L_INOTIFY, "The %s %s was %s.\n", (event->mask & IN_ISDIR ? "directory" : "file"), path_buf, (event->mask & IN_MOVED_FROM ? "moved away" : "deleted")); if ( event->mask & IN_ISDIR ) inotify_remove_directory(pollfds[0].fd, path_buf); else inotify_remove_file(path_buf); } free(esc_name); } i += EVENT_SIZE + event->len; } } inotify_remove_watches(pollfds[0].fd); quitting: close(pollfds[0].fd); return 0; } #endif minidlna-1.1.5+dfsg/inotify.h000066400000000000000000000001401261774340000160610ustar00rootroot00000000000000#ifdef HAVE_INOTIFY int inotify_remove_file(const char * path); void * start_inotify(); #endif minidlna-1.1.5+dfsg/libav.h000066400000000000000000000117051261774340000155060ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2013 NETGEAR * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #if HAVE_FFMPEG_LIBAVUTIL_AVUTIL_H #include #elif HAVE_LIBAV_LIBAVUTIL_AVUTIL_H #include #elif HAVE_LIBAVUTIL_AVUTIL_H #include #elif HAVE_FFMPEG_AVUTIL_H #include #elif HAVE_LIBAV_AVUTIL_H #include #elif HAVE_AVUTIL_H #include #endif #if HAVE_FFMPEG_LIBAVCODEC_AVCODEC_H #include #elif HAVE_LIBAV_LIBAVCODEC_AVCODEC_H #include #elif HAVE_LIBAVCODEC_AVCODEC_H #include #elif HAVE_FFMPEG_AVCODEC_H #include #elif HAVE_LIBAV_AVCODEC_H #include #elif HAVE_AVCODEC_H #include #endif #if HAVE_FFMPEG_LIBAVFORMAT_AVFORMAT_H #include #elif HAVE_LIBAV_LIBAVFORMAT_AVFORMAT_H #include #elif HAVE_LIBAVFORMAT_AVFORMAT_H #include #elif HAVE_FFMPEG_AVFORMAT_H #include #elif HAVE_LIBAV_LIBAVFORMAT_H #include #elif HAVE_AVFORMAT_H #include #endif #ifndef FF_PROFILE_H264_BASELINE #define FF_PROFILE_H264_BASELINE 66 #endif #ifndef FF_PROFILE_H264_CONSTRAINED_BASELINE #define FF_PROFILE_H264_CONSTRAINED_BASELINE 578 #endif #ifndef FF_PROFILE_H264_MAIN #define FF_PROFILE_H264_MAIN 77 #endif #ifndef FF_PROFILE_H264_HIGH #define FF_PROFILE_H264_HIGH 100 #endif #ifndef FF_PROFILE_SKIP #define FF_PROFILE_SKIP -100 #endif #if LIBAVCODEC_VERSION_MAJOR < 53 #define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO #define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO #endif #if LIBAVCODEC_VERSION_INT <= ((51<<16)+(50<<8)+1) #define CODEC_ID_WMAPRO CODEC_ID_NONE #endif #if LIBAVCODEC_VERSION_MAJOR < 55 #define AV_CODEC_ID_AAC CODEC_ID_AAC #define AV_CODEC_ID_AC3 CODEC_ID_AC3 #define AV_CODEC_ID_ADPCM_IMA_QT CODEC_ID_ADPCM_IMA_QT #define AV_CODEC_ID_AMR_NB CODEC_ID_AMR_NB #define AV_CODEC_ID_DTS CODEC_ID_DTS #define AV_CODEC_ID_H264 CODEC_ID_H264 #define AV_CODEC_ID_MP2 CODEC_ID_MP2 #define AV_CODEC_ID_MP3 CODEC_ID_MP3 #define AV_CODEC_ID_MPEG1VIDEO CODEC_ID_MPEG1VIDEO #define AV_CODEC_ID_MPEG2VIDEO CODEC_ID_MPEG2VIDEO #define AV_CODEC_ID_MPEG4 CODEC_ID_MPEG4 #define AV_CODEC_ID_MSMPEG4V3 CODEC_ID_MSMPEG4V3 #define AV_CODEC_ID_PCM_S16LE CODEC_ID_PCM_S16LE #define AV_CODEC_ID_VC1 CODEC_ID_VC1 #define AV_CODEC_ID_WMAPRO CODEC_ID_WMAPRO #define AV_CODEC_ID_WMAV1 CODEC_ID_WMAV1 #define AV_CODEC_ID_WMAV2 CODEC_ID_WMAV2 #define AV_CODEC_ID_WMV3 CODEC_ID_WMV3 #endif #if LIBAVUTIL_VERSION_INT < ((50<<16)+(13<<8)+0) #define av_strerror(x, y, z) snprintf(y, z, "%d", x) #endif #if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) # if LIBAVUTIL_VERSION_INT < ((51<<16)+(5<<8)+0) && !defined(FF_API_OLD_METADATA2) #define AV_DICT_IGNORE_SUFFIX AV_METADATA_IGNORE_SUFFIX #define av_dict_get av_metadata_get typedef AVMetadataTag AVDictionaryEntry; # endif #endif static inline int lav_open(AVFormatContext **ctx, const char *filename) { int ret; #if LIBAVFORMAT_VERSION_INT >= ((53<<16)+(17<<8)+0) ret = avformat_open_input(ctx, filename, NULL, NULL); if (ret == 0) avformat_find_stream_info(*ctx, NULL); #else ret = av_open_input_file(ctx, filename, NULL, 0, NULL); if (ret == 0) av_find_stream_info(*ctx); #endif return ret; } static inline void lav_close(AVFormatContext *ctx) { #if LIBAVFORMAT_VERSION_INT >= ((53<<16)+(17<<8)+0) avformat_close_input(&ctx); #else av_close_input_file(ctx); #endif } static inline int lav_get_fps(AVStream *s) { #if LIBAVCODEC_VERSION_MAJOR < 54 if (s->r_frame_rate.den) return s->r_frame_rate.num / s->r_frame_rate.den; #else if (s->avg_frame_rate.den) return s->avg_frame_rate.num / s->avg_frame_rate.den; #endif return 0; } static inline int lav_get_interlaced(AVCodecContext *vc, AVStream *s) { #if LIBAVCODEC_VERSION_MAJOR < 54 return (vc->time_base.den ? (s->r_frame_rate.num / vc->time_base.den) : 0); #else return (vc->time_base.den ? (s->avg_frame_rate.num / vc->time_base.den) : 0); #endif } static inline int lav_is_thumbnail_stream(AVStream *s, uint8_t **data, int *size) { #if LIBAVFORMAT_VERSION_INT >= ((54<<16)+(6<<8)) if (s->disposition & AV_DISPOSITION_ATTACHED_PIC && s->codec->codec_id == AV_CODEC_ID_MJPEG) { if (data) *data = s->attached_pic.data; if (size) *size = s->attached_pic.size; return 1; } #endif return 0; } minidlna-1.1.5+dfsg/linux/000077500000000000000000000000001261774340000153735ustar00rootroot00000000000000minidlna-1.1.5+dfsg/linux/inotify-syscalls.h000066400000000000000000000045001261774340000210570ustar00rootroot00000000000000#ifndef _LINUX_INOTIFY_SYSCALLS_H #define _LINUX_INOTIFY_SYSCALLS_H #include #if defined(__i386__) # define __NR_inotify_init 291 # define __NR_inotify_add_watch 292 # define __NR_inotify_rm_watch 293 #elif defined(__x86_64__) # define __NR_inotify_init 253 # define __NR_inotify_add_watch 254 # define __NR_inotify_rm_watch 255 #elif defined(__powerpc__) || defined(__powerpc64__) # define __NR_inotify_init 275 # define __NR_inotify_add_watch 276 # define __NR_inotify_rm_watch 277 #elif defined (__mips__) # if _MIPS_SIM == _MIPS_SIM_ABI32 # define __NR_inotify_init (__NR_Linux + 284) # define __NR_inotify_add_watch (__NR_Linux + 285) # define __NR_inotify_rm_watch (__NR_Linux + 286) # endif # if _MIPS_SIM == _MIPS_SIM_ABI64 # define __NR_inotify_init (__NR_Linux + 243) # define __NR_inotify_add_watch (__NR_Linux + 243) # define __NR_inotify_rm_watch (__NR_Linux + 243) # endif # if _MIPS_SIM == _MIPS_SIM_NABI32 # define __NR_inotify_init (__NR_Linux + 247) # define __NR_inotify_add_watch (__NR_Linux + 248) # define __NR_inotify_rm_watch (__NR_Linux + 249) # endif #elif defined (__ia64__) # define __NR_inotify_init 1277 # define __NR_inotify_add_watch 1278 # define __NR_inotify_rm_watch 1279 #elif defined (__s390__) # define __NR_inotify_init 284 # define __NR_inotify_add_watch 285 # define __NR_inotify_rm_watch 286 #elif defined (__alpha__) # define __NR_inotify_init 444 # define __NR_inotify_add_watch 445 # define __NR_inotify_rm_watch 446 #elif defined (__sparc__) || defined (__sparc64__) # define __NR_inotify_init 151 # define __NR_inotify_add_watch 152 # define __NR_inotify_rm_watch 156 #elif defined (__arm__) # define __NR_inotify_init (__NR_SYSCALL_BASE+316) # define __NR_inotify_add_watch (__NR_SYSCALL_BASE+317) # define __NR_inotify_rm_watch (__NR_SYSCALL_BASE+318) #elif defined (__sh__) # define __NR_inotify_init 290 # define __NR_inotify_add_watch 291 # define __NR_inotify_rm_watch 292 #else # error "Unsupported architecture!" #endif static inline int inotify_init (void) { return syscall (__NR_inotify_init); } static inline int inotify_add_watch (int fd, const char *name, __u32 mask) { return syscall (__NR_inotify_add_watch, fd, name, mask); } static inline int inotify_rm_watch (int fd, __u32 wd) { return syscall (__NR_inotify_rm_watch, fd, wd); } #endif /* _LINUX_INOTIFY_SYSCALLS_H */ minidlna-1.1.5+dfsg/linux/inotify.h000066400000000000000000000064721261774340000172360ustar00rootroot00000000000000/* * Inode based directory notification for Linux * * Copyright (C) 2005 John McCutchan */ #ifndef _LINUX_INOTIFY_H #define _LINUX_INOTIFY_H #include /* * struct inotify_event - structure read from the inotify device for each event * * When you are watching a directory, you will receive the filename for events * such as IN_CREATE, IN_DELETE, IN_OPEN, IN_CLOSE, ..., relative to the wd. */ struct inotify_event { __s32 wd; /* watch descriptor */ __u32 mask; /* watch mask */ __u32 cookie; /* cookie to synchronize two events */ __u32 len; /* length (including nulls) of name */ char name[0]; /* stub for possible name */ }; /* the following are legal, implemented events that user-space can watch for */ #define IN_ACCESS 0x00000001 /* File was accessed */ #define IN_MODIFY 0x00000002 /* File was modified */ #define IN_ATTRIB 0x00000004 /* Metadata changed */ #define IN_CLOSE_WRITE 0x00000008 /* Writtable file was closed */ #define IN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed */ #define IN_OPEN 0x00000020 /* File was opened */ #define IN_MOVED_FROM 0x00000040 /* File was moved from X */ #define IN_MOVED_TO 0x00000080 /* File was moved to Y */ #define IN_CREATE 0x00000100 /* Subfile was created */ #define IN_DELETE 0x00000200 /* Subfile was deleted */ #define IN_DELETE_SELF 0x00000400 /* Self was deleted */ /* the following are legal events. they are sent as needed to any watch */ #define IN_UNMOUNT 0x00002000 /* Backing fs was unmounted */ #define IN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */ #define IN_IGNORED 0x00008000 /* File was ignored */ /* helper events */ #define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* close */ #define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO) /* moves */ /* special flags */ #define IN_ISDIR 0x40000000 /* event occurred against dir */ #define IN_ONESHOT 0x80000000 /* only send event once */ /* * All of the events - we build the list by hand so that we can add flags in * the future and not break backward compatibility. Apps will get only the * events that they originally wanted. Be sure to add new events here! */ #define IN_ALL_EVENTS (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \ IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | \ IN_MOVED_TO | IN_DELETE | IN_CREATE | IN_DELETE_SELF) #ifdef __KERNEL__ #include #include #include #ifdef CONFIG_INOTIFY extern void inotify_inode_queue_event(struct inode *, __u32, __u32, const char *); extern void inotify_dentry_parent_queue_event(struct dentry *, __u32, __u32, const char *); extern void inotify_unmount_inodes(struct list_head *); extern void inotify_inode_is_dead(struct inode *); extern u32 inotify_get_cookie(void); #else static inline void inotify_inode_queue_event(struct inode *inode, __u32 mask, __u32 cookie, const char *filename) { } static inline void inotify_dentry_parent_queue_event(struct dentry *dentry, __u32 mask, __u32 cookie, const char *filename) { } static inline void inotify_unmount_inodes(struct list_head *list) { } static inline void inotify_inode_is_dead(struct inode *inode) { } static inline u32 inotify_get_cookie(void) { return 0; } #endif /* CONFIG_INOTIFY */ #endif /* __KERNEL __ */ #endif /* _LINUX_INOTIFY_H */ minidlna-1.1.5+dfsg/linux/minidlna.init.d.script.tmpl000066400000000000000000000031271261774340000225560ustar00rootroot00000000000000#!/bin/sh # chkconfig: 345 99 10 # description: Startup/shutdown script for MiniDLNA daemon # # Based on the MiniUPnPd script by Thomas Bernard # Modified for MiniDLNA by Justin Maggard # Status function added by Igor Drobot # ### BEGIN INIT INFO # Provides: minidlna # Required-Start: $network $local_fs $remote_fs # Required-Stop:: $network $local_fs $remote_fs # Should-Start: $all # Should-Stop: $all # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: DLNA/UPnP-AV media server ### END INIT INFO MINIDLNA=:SBINDIR:/minidlnad PIDFILE=/var/run/minidlna/minidlna.pid CONF=/etc/minidlna.conf ARGS="-f $CONF" test -f $MINIDLNA || exit 0 . /lib/lsb/init-functions case "$1" in start) log_daemon_msg "Starting minidlna" "minidlna" start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $MINIDLNA -- $ARGS $LSBNAMES log_end_msg $? ;; stop) log_daemon_msg "Stopping minidlna" "minidlna" start-stop-daemon --stop --quiet --pidfile $PIDFILE log_end_msg $? ;; restart|reload|force-reload) log_daemon_msg "Restarting minidlna" "minidlna" start-stop-daemon --stop --retry 5 --quiet --pidfile $PIDFILE start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $MINIDLNA -- $ARGS $LSBNAMES log_end_msg $? ;; status) status_of_proc -p $PIDFILE $MINIDLNA minidlna && exit 0 || exit $? ;; *) log_action_msg "Usage: /etc/init.d/minidlna {start|stop|restart|reload|force-reload|status}" exit 2 ;; esac exit 0 minidlna-1.1.5+dfsg/log.c000066400000000000000000000071711261774340000151670ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2008-2010 NETGEAR, Inc. All Rights Reserved. * * This file is part of MiniDLNA. * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include #include #include #include #include #include "upnpglobalvars.h" #include "log.h" static FILE *log_fp = NULL; static const int _default_log_level = E_WARN; int log_level[L_MAX]; const char *facility_name[] = { "general", "artwork", "database", "inotify", "scanner", "metadata", "http", "ssdp", "tivo", 0 }; const char *level_name[] = { "off", // E_OFF "fatal", // E_FATAL "error", // E_ERROR "warn", // E_WARN "info", // E_INFO "debug", // E_DEBUG "maxdebug", // E_MAXDEBUG 0 }; void log_close(void) { if (log_fp) fclose(log_fp); } int find_matching_name(const char* str, const char* names[]) { if (str == NULL) return -1; const char* start = strpbrk(str, ",="); int level, c = (start != NULL) ? start - str : strlen(str); for (level = 0; names[level] != 0; level++) { if (!(strncasecmp(names[level], str, c))) return level; } return -1; } int log_init(const char *fname, const char *debug) { int i; FILE *fp; int level = find_matching_name(debug, level_name); int default_log_level = (level == -1) ? _default_log_level : level; for (i=0; ilog_level[facility] && level>E_FATAL) return; if (!log_fp) log_fp = stdout; // timestamp if (!GETFLAG(SYSTEMD_MASK)) { time_t t; struct tm *tm; t = time(NULL); tm = localtime(&t); fprintf(log_fp, "[%04d/%02d/%02d %02d:%02d:%02d] ", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } if (level) fprintf(log_fp, "%s:%d: %s: ", fname, lineno, level_name[level]); else fprintf(log_fp, "%s:%d: ", fname, lineno); // user log va_start(ap, fmt); if (vfprintf(log_fp, fmt, ap) == -1) { va_end(ap); return; } va_end(ap); fflush(log_fp); if (level==E_FATAL) exit(-1); return; } minidlna-1.1.5+dfsg/log.h000066400000000000000000000030761261774340000151740ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2008-2010 NETGEAR, Inc. All Rights Reserved. * * This file is part of MiniDLNA. * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __ERR_H__ #define __ERR_H__ #define E_OFF 0 #define E_FATAL 1 #define E_ERROR 2 #define E_WARN 3 #define E_INFO 4 #define E_DEBUG 5 #define E_MAXDEBUG 6 enum _log_facility { L_GENERAL=0, L_ARTWORK, L_DB_SQL, L_INOTIFY, L_SCANNER, L_METADATA, L_HTTP, L_SSDP, L_TIVO, L_MAX }; extern int log_level[L_MAX]; extern int log_init(const char *fname, const char *debug); extern void log_close(void); extern void log_err(int level, enum _log_facility facility, char *fname, int lineno, char *fmt, ...) __attribute__((__format__ (__printf__, 5, 6))); #define DPRINTF(level, facility, fmt, arg...) do { log_err(level, facility, __FILE__, __LINE__, fmt, ##arg); } while (0) #endif /* __ERR_H__ */ minidlna-1.1.5+dfsg/metadata.c000066400000000000000000001247571261774340000162000ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libav.h" #include "upnpglobalvars.h" #include "tagutils/tagutils.h" #include "image_utils.h" #include "upnpreplyparse.h" #include "tivo_utils.h" #include "metadata.h" #include "albumart.h" #include "utils.h" #include "sql.h" #include "log.h" #define FLAG_TITLE 0x00000001 #define FLAG_ARTIST 0x00000002 #define FLAG_ALBUM 0x00000004 #define FLAG_GENRE 0x00000008 #define FLAG_COMMENT 0x00000010 #define FLAG_CREATOR 0x00000020 #define FLAG_DATE 0x00000040 #define FLAG_DLNA_PN 0x00000080 #define FLAG_MIME 0x00000100 #define FLAG_DURATION 0x00000200 #define FLAG_RESOLUTION 0x00000400 /* Audio profile flags */ enum audio_profiles { PROFILE_AUDIO_UNKNOWN, PROFILE_AUDIO_MP3, PROFILE_AUDIO_AC3, PROFILE_AUDIO_WMA_BASE, PROFILE_AUDIO_WMA_FULL, PROFILE_AUDIO_WMA_PRO, PROFILE_AUDIO_MP2, PROFILE_AUDIO_PCM, PROFILE_AUDIO_AAC, PROFILE_AUDIO_AAC_MULT5, PROFILE_AUDIO_AMR }; /* This function shamelessly copied from libdlna */ #define MPEG_TS_SYNC_CODE 0x47 #define MPEG_TS_PACKET_LENGTH 188 #define MPEG_TS_PACKET_LENGTH_DLNA 192 /* prepends 4 bytes to TS packet */ int dlna_timestamp_is_present(const char *filename, int *raw_packet_size) { unsigned char buffer[3*MPEG_TS_PACKET_LENGTH_DLNA]; int fd, i; /* read file header */ fd = open(filename, O_RDONLY); if( fd < 0 ) return 0; i = read(fd, buffer, MPEG_TS_PACKET_LENGTH_DLNA*3); close(fd); if( i < 0 ) return 0; for( i = 0; i < MPEG_TS_PACKET_LENGTH_DLNA; i++ ) { if( buffer[i] == MPEG_TS_SYNC_CODE ) { if (buffer[i + MPEG_TS_PACKET_LENGTH_DLNA] == MPEG_TS_SYNC_CODE && buffer[i + MPEG_TS_PACKET_LENGTH_DLNA*2] == MPEG_TS_SYNC_CODE) { *raw_packet_size = MPEG_TS_PACKET_LENGTH_DLNA; if (buffer[i+MPEG_TS_PACKET_LENGTH] == 0x00 && buffer[i+MPEG_TS_PACKET_LENGTH+1] == 0x00 && buffer[i+MPEG_TS_PACKET_LENGTH+2] == 0x00 && buffer[i+MPEG_TS_PACKET_LENGTH+3] == 0x00) return 0; else return 1; } else if (buffer[i + MPEG_TS_PACKET_LENGTH] == MPEG_TS_SYNC_CODE && buffer[i + MPEG_TS_PACKET_LENGTH*2] == MPEG_TS_SYNC_CODE) { *raw_packet_size = MPEG_TS_PACKET_LENGTH; return 0; } } } *raw_packet_size = 0; return 0; } void check_for_captions(const char *path, int64_t detailID) { char file[MAXPATHLEN]; char *p; int ret; strncpyt(file, path, sizeof(file)); p = strip_ext(file); if (!p) p = strrchr(file, '\0'); /* If we weren't given a detail ID, look for one. */ if (!detailID) { detailID = sql_get_int64_field(db, "SELECT ID from DETAILS where (PATH > '%q.' and PATH <= '%q.z')" " and MIME glob 'video/*' limit 1", file, file); if (detailID <= 0) { //DPRINTF(E_MAXDEBUG, L_METADATA, "No file found for caption %s.\n", path); return; } } strcpy(p, ".srt"); ret = access(file, R_OK); if (ret != 0) { strcpy(p, ".smi"); ret = access(file, R_OK); } if (ret == 0) { sql_exec(db, "INSERT into CAPTIONS" " (ID, PATH) " "VALUES" " (%lld, %Q)", detailID, file); } } void parse_nfo(const char *path, metadata_t *m) { FILE *nfo; char buf[65536]; struct NameValueParserData xml; struct stat file; size_t nread; char *val, *val2; if( stat(path, &file) != 0 || file.st_size > 65536 ) { DPRINTF(E_INFO, L_METADATA, "Not parsing very large .nfo file %s\n", path); return; } DPRINTF(E_DEBUG, L_METADATA, "Parsing .nfo file: %s\n", path); nfo = fopen(path, "r"); if( !nfo ) return; nread = fread(&buf, 1, sizeof(buf), nfo); ParseNameValue(buf, nread, &xml, 0); //printf("\ttype: %s\n", GetValueFromNameValueList(&xml, "rootElement")); val = GetValueFromNameValueList(&xml, "title"); if( val ) { char *esc_tag, *title; val2 = GetValueFromNameValueList(&xml, "episodetitle"); if( val2 ) xasprintf(&title, "%s - %s", val, val2); else title = strdup(val); esc_tag = unescape_tag(title, 1); m->title = escape_tag(esc_tag, 1); free(esc_tag); free(title); } val = GetValueFromNameValueList(&xml, "plot"); if( val ) { char *esc_tag = unescape_tag(val, 1); m->comment = escape_tag(esc_tag, 1); free(esc_tag); } val = GetValueFromNameValueList(&xml, "capturedate"); if( val ) { char *esc_tag = unescape_tag(val, 1); m->date = escape_tag(esc_tag, 1); free(esc_tag); } val = GetValueFromNameValueList(&xml, "genre"); if( val ) { free(m->genre); char *esc_tag = unescape_tag(val, 1); m->genre = escape_tag(esc_tag, 1); free(esc_tag); } val = GetValueFromNameValueList(&xml, "mime"); if( val ) { free(m->mime); char *esc_tag = unescape_tag(val, 1); m->mime = escape_tag(esc_tag, 1); free(esc_tag); } ClearNameValueList(&xml); fclose(nfo); } void free_metadata(metadata_t *m, uint32_t flags) { if( flags & FLAG_TITLE ) free(m->title); if( flags & FLAG_ARTIST ) free(m->artist); if( flags & FLAG_ALBUM ) free(m->album); if( flags & FLAG_GENRE ) free(m->genre); if( flags & FLAG_CREATOR ) free(m->creator); if( flags & FLAG_DATE ) free(m->date); if( flags & FLAG_COMMENT ) free(m->comment); if( flags & FLAG_DLNA_PN ) free(m->dlna_pn); if( flags & FLAG_MIME ) free(m->mime); if( flags & FLAG_DURATION ) free(m->duration); if( flags & FLAG_RESOLUTION ) free(m->resolution); } int64_t GetFolderMetadata(const char *name, const char *path, const char *artist, const char *genre, int64_t album_art) { int ret; ret = sql_exec(db, "INSERT into DETAILS" " (TITLE, PATH, CREATOR, ARTIST, GENRE, ALBUM_ART) " "VALUES" " ('%q', %Q, %Q, %Q, %Q, %lld);", name, path, artist, artist, genre, album_art); if( ret != SQLITE_OK ) ret = 0; else ret = sqlite3_last_insert_rowid(db); return ret; } int64_t GetAudioMetadata(const char *path, char *name) { char type[4]; static char lang[6] = { '\0' }; struct stat file; int64_t ret; char *esc_tag; int i; int64_t album_art = 0; struct song_metadata song; metadata_t m; uint32_t free_flags = FLAG_MIME|FLAG_DURATION|FLAG_DLNA_PN|FLAG_DATE; memset(&m, '\0', sizeof(metadata_t)); if ( stat(path, &file) != 0 ) return 0; strip_ext(name); if( ends_with(path, ".mp3") ) { strcpy(type, "mp3"); m.mime = strdup("audio/mpeg"); } else if( ends_with(path, ".m4a") || ends_with(path, ".mp4") || ends_with(path, ".aac") || ends_with(path, ".m4p") ) { strcpy(type, "aac"); m.mime = strdup("audio/mp4"); } else if( ends_with(path, ".3gp") ) { strcpy(type, "aac"); m.mime = strdup("audio/3gpp"); } else if( ends_with(path, ".wma") || ends_with(path, ".asf") ) { strcpy(type, "asf"); m.mime = strdup("audio/x-ms-wma"); } else if( ends_with(path, ".flac") || ends_with(path, ".fla") || ends_with(path, ".flc") ) { strcpy(type, "flc"); m.mime = strdup("audio/x-flac"); } else if( ends_with(path, ".wav") ) { strcpy(type, "wav"); m.mime = strdup("audio/x-wav"); } else if( ends_with(path, ".ogg") || ends_with(path, ".oga") ) { strcpy(type, "ogg"); m.mime = strdup("audio/ogg"); } else if( ends_with(path, ".pcm") ) { strcpy(type, "pcm"); m.mime = strdup("audio/L16"); } else { DPRINTF(E_WARN, L_METADATA, "Unhandled file extension on %s\n", path); return 0; } if( !(*lang) ) { if( !getenv("LANG") ) strcpy(lang, "en_US"); else strncpyt(lang, getenv("LANG"), sizeof(lang)); } if( readtags((char *)path, &song, &file, lang, type) != 0 ) { DPRINTF(E_WARN, L_METADATA, "Cannot extract tags from %s!\n", path); freetags(&song); free_metadata(&m, free_flags); return 0; } if( song.dlna_pn ) m.dlna_pn = strdup(song.dlna_pn); if( song.year ) xasprintf(&m.date, "%04d-01-01", song.year); xasprintf(&m.duration, "%d:%02d:%02d.%03d", (song.song_length/3600000), (song.song_length/60000%60), (song.song_length/1000%60), (song.song_length%1000)); if( song.title && *song.title ) { m.title = trim(song.title); if( (esc_tag = escape_tag(m.title, 0)) ) { free_flags |= FLAG_TITLE; m.title = esc_tag; } } else { m.title = name; } for( i = ROLE_START; i < N_ROLE; i++ ) { if( song.contributor[i] && *song.contributor[i] ) { m.creator = trim(song.contributor[i]); if( strlen(m.creator) > 48 ) { m.creator = strdup("Various Artists"); free_flags |= FLAG_CREATOR; } else if( (esc_tag = escape_tag(m.creator, 0)) ) { m.creator = esc_tag; free_flags |= FLAG_CREATOR; } m.artist = m.creator; break; } } /* If there is a album artist or band associated with the album, use it for virtual containers. */ if( i < ROLE_ALBUMARTIST ) { for( i = ROLE_ALBUMARTIST; i <= ROLE_BAND; i++ ) { if( song.contributor[i] && *song.contributor[i] ) break; } if( i <= ROLE_BAND ) { m.artist = trim(song.contributor[i]); if( strlen(m.artist) > 48 ) { m.artist = strdup("Various Artists"); free_flags |= FLAG_ARTIST; } else if( (esc_tag = escape_tag(m.artist, 0)) ) { m.artist = esc_tag; free_flags |= FLAG_ARTIST; } } } if( song.album && *song.album ) { m.album = trim(song.album); if( (esc_tag = escape_tag(m.album, 0)) ) { free_flags |= FLAG_ALBUM; m.album = esc_tag; } } if( song.genre && *song.genre ) { m.genre = trim(song.genre); if( (esc_tag = escape_tag(m.genre, 0)) ) { free_flags |= FLAG_GENRE; m.genre = esc_tag; } } if( song.comment && *song.comment ) { m.comment = trim(song.comment); if( (esc_tag = escape_tag(m.comment, 0)) ) { free_flags |= FLAG_COMMENT; m.comment = esc_tag; } } album_art = find_album_art(path, song.image, song.image_size); ret = sql_exec(db, "INSERT into DETAILS" " (PATH, SIZE, TIMESTAMP, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE," " TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, DISC, TRACK, DLNA_PN, MIME, ALBUM_ART) " "VALUES" " (%Q, %lld, %lld, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, %d, %Q, '%s', %lld);", path, (long long)file.st_size, (long long)file.st_mtime, m.duration, song.channels, song.bitrate, song.samplerate, m.date, m.title, m.creator, m.artist, m.album, m.genre, m.comment, song.disc, song.track, m.dlna_pn, song.mime?song.mime:m.mime, album_art); if( ret != SQLITE_OK ) { DPRINTF(E_ERROR, L_METADATA, "Error inserting details for '%s'!\n", path); ret = 0; } else { ret = sqlite3_last_insert_rowid(db); } freetags(&song); free_metadata(&m, free_flags); return ret; } /* For libjpeg error handling */ jmp_buf setjmp_buffer; static void libjpeg_error_handler(j_common_ptr cinfo) { cinfo->err->output_message (cinfo); longjmp(setjmp_buffer, 1); return; } int64_t GetImageMetadata(const char *path, char *name) { ExifData *ed; ExifEntry *e = NULL; ExifLoader *l; struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; FILE *infile; int width=0, height=0, thumb=0; char make[32], model[64] = {'\0'}; char b[1024]; struct stat file; int64_t ret; image_s *imsrc; metadata_t m; uint32_t free_flags = 0xFFFFFFFF; memset(&m, '\0', sizeof(metadata_t)); //DEBUG DPRINTF(E_DEBUG, L_METADATA, "Parsing %s...\n", path); if ( stat(path, &file) != 0 ) return 0; strip_ext(name); //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * size: %jd\n", file.st_size); /* MIME hard-coded to JPEG for now, until we add PNG support */ m.mime = strdup("image/jpeg"); l = exif_loader_new(); exif_loader_write_file(l, path); ed = exif_loader_get_data(l); exif_loader_unref(l); if( !ed ) goto no_exifdata; e = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL); if( e || (e = exif_content_get_entry(ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_DIGITIZED)) ) { m.date = strdup(exif_entry_get_value(e, b, sizeof(b))); if( strlen(m.date) > 10 ) { m.date[4] = '-'; m.date[7] = '-'; m.date[10] = 'T'; } else { free(m.date); m.date = NULL; } } else { /* One last effort to get the date from XMP */ image_get_jpeg_date_xmp(path, &m.date); } //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * date: %s\n", m.date); e = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_MAKE); if( e ) { strncpyt(make, exif_entry_get_value(e, b, sizeof(b)), sizeof(make)); e = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_MODEL); if( e ) { strncpyt(model, exif_entry_get_value(e, b, sizeof(b)), sizeof(model)); if( !strcasestr(model, make) ) snprintf(model, sizeof(model), "%s %s", make, exif_entry_get_value(e, b, sizeof(b))); m.creator = escape_tag(trim(model), 1); } } //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * model: %s\n", model); e = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION); if( e ) { switch( exif_get_short(e->data, exif_data_get_byte_order(ed)) ) { case 3: m.rotation = 180; break; case 6: m.rotation = 90; break; case 8: m.rotation = 270; break; default: m.rotation = 0; break; } } if( ed->size ) { /* We might need to verify that the thumbnail is 160x160 or smaller */ if( ed->size > 12000 ) { imsrc = image_new_from_jpeg(NULL, 0, ed->data, ed->size, 1, ROTATE_NONE); if( imsrc ) { if( (imsrc->width <= 160) && (imsrc->height <= 160) ) thumb = 1; image_free(imsrc); } } else thumb = 1; } //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * thumbnail: %d\n", thumb); exif_data_unref(ed); no_exifdata: /* If SOF parsing fails, then fall through to reading the JPEG data with libjpeg to get the resolution */ if( image_get_jpeg_resolution(path, &width, &height) != 0 || !width || !height ) { infile = fopen(path, "r"); if( infile ) { cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = libjpeg_error_handler; jpeg_create_decompress(&cinfo); if( setjmp(setjmp_buffer) ) goto error; jpeg_stdio_src(&cinfo, infile); jpeg_read_header(&cinfo, TRUE); jpeg_start_decompress(&cinfo); width = cinfo.output_width; height = cinfo.output_height; error: jpeg_destroy_decompress(&cinfo); fclose(infile); } } //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * resolution: %dx%d\n", width, height); if( !width || !height ) { free_metadata(&m, free_flags); return 0; } if( width <= 640 && height <= 480 ) m.dlna_pn = strdup("JPEG_SM"); else if( width <= 1024 && height <= 768 ) m.dlna_pn = strdup("JPEG_MED"); else if( (width <= 4096 && height <= 4096) || !GETFLAG(DLNA_STRICT_MASK) ) m.dlna_pn = strdup("JPEG_LRG"); xasprintf(&m.resolution, "%dx%d", width, height); ret = sql_exec(db, "INSERT into DETAILS" " (PATH, TITLE, SIZE, TIMESTAMP, DATE, RESOLUTION," " ROTATION, THUMBNAIL, CREATOR, DLNA_PN, MIME) " "VALUES" " (%Q, '%q', %lld, %lld, %Q, %Q, %u, %d, %Q, %Q, %Q);", path, name, (long long)file.st_size, (long long)file.st_mtime, m.date, m.resolution, m.rotation, thumb, m.creator, m.dlna_pn, m.mime); if( ret != SQLITE_OK ) { DPRINTF(E_ERROR, L_METADATA, "Error inserting details for '%s'!\n", path); ret = 0; } else { ret = sqlite3_last_insert_rowid(db); } free_metadata(&m, free_flags); return ret; } int64_t GetVideoMetadata(const char *path, char *name) { struct stat file; int ret, i; struct tm *modtime; AVFormatContext *ctx = NULL; AVCodecContext *ac = NULL, *vc = NULL; int audio_stream = -1, video_stream = -1; enum audio_profiles audio_profile = PROFILE_AUDIO_UNKNOWN; char fourcc[4]; int64_t album_art = 0; char nfo[MAXPATHLEN], *ext; struct song_metadata video; metadata_t m; uint32_t free_flags = 0xFFFFFFFF; char *path_cpy, *basepath; memset(&m, '\0', sizeof(m)); memset(&video, '\0', sizeof(video)); //DEBUG DPRINTF(E_DEBUG, L_METADATA, "Parsing video %s...\n", name); if ( stat(path, &file) != 0 ) return 0; strip_ext(name); //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * size: %jd\n", file.st_size); ret = lav_open(&ctx, path); if( ret != 0 ) { char err[128]; av_strerror(ret, err, sizeof(err)); DPRINTF(E_WARN, L_METADATA, "Opening %s failed! [%s]\n", path, err); return 0; } //dump_format(ctx, 0, NULL, 0); for( i=0; inb_streams; i++) { if( ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audio_stream == -1 ) { audio_stream = i; ac = ctx->streams[audio_stream]->codec; continue; } else if( ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && !lav_is_thumbnail_stream(ctx->streams[i], &m.thumb_data, &m.thumb_size) && video_stream == -1 ) { video_stream = i; vc = ctx->streams[video_stream]->codec; continue; } } path_cpy = strdup(path); basepath = basename(path_cpy); if( !vc ) { /* This must not be a video file. */ lav_close(ctx); if( !is_audio(path) ) DPRINTF(E_DEBUG, L_METADATA, "File %s does not contain a video stream.\n", basepath); free(path_cpy); return 0; } if( ac ) { aac_object_type_t aac_type = AAC_INVALID; switch( ac->codec_id ) { case AV_CODEC_ID_MP3: audio_profile = PROFILE_AUDIO_MP3; break; case AV_CODEC_ID_AAC: if( !ac->extradata_size || !ac->extradata ) { DPRINTF(E_DEBUG, L_METADATA, "No AAC type\n"); } else { uint8_t data; memcpy(&data, ac->extradata, 1); aac_type = data >> 3; } switch( aac_type ) { /* AAC Low Complexity variants */ case AAC_LC: case AAC_LC_ER: if( ac->sample_rate < 8000 || ac->sample_rate > 48000 ) { DPRINTF(E_DEBUG, L_METADATA, "Unsupported AAC: sample rate is not 8000 < %d < 48000\n", ac->sample_rate); break; } /* AAC @ Level 1/2 */ if( ac->channels <= 2 && ac->bit_rate <= 576000 ) audio_profile = PROFILE_AUDIO_AAC; else if( ac->channels <= 6 && ac->bit_rate <= 1440000 ) audio_profile = PROFILE_AUDIO_AAC_MULT5; else DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC: %d channels, %d bitrate\n", ac->channels, ac->bit_rate); break; default: DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC type [%d]\n", aac_type); break; } break; case AV_CODEC_ID_AC3: case AV_CODEC_ID_DTS: audio_profile = PROFILE_AUDIO_AC3; break; case AV_CODEC_ID_WMAV1: case AV_CODEC_ID_WMAV2: /* WMA Baseline: stereo, up to 48 KHz, up to 192,999 bps */ if ( ac->bit_rate <= 193000 ) audio_profile = PROFILE_AUDIO_WMA_BASE; /* WMA Full: stereo, up to 48 KHz, up to 385 Kbps */ else if ( ac->bit_rate <= 385000 ) audio_profile = PROFILE_AUDIO_WMA_FULL; break; case AV_CODEC_ID_WMAPRO: audio_profile = PROFILE_AUDIO_WMA_PRO; break; case AV_CODEC_ID_MP2: audio_profile = PROFILE_AUDIO_MP2; break; case AV_CODEC_ID_AMR_NB: audio_profile = PROFILE_AUDIO_AMR; break; default: if( (ac->codec_id >= AV_CODEC_ID_PCM_S16LE) && (ac->codec_id < AV_CODEC_ID_ADPCM_IMA_QT) ) audio_profile = PROFILE_AUDIO_PCM; else DPRINTF(E_DEBUG, L_METADATA, "Unhandled audio codec [0x%X]\n", ac->codec_id); break; } m.frequency = ac->sample_rate; m.channels = ac->channels; } if( vc ) { int off; int duration, hours, min, sec, ms; ts_timestamp_t ts_timestamp = NONE; DPRINTF(E_DEBUG, L_METADATA, "Container: '%s' [%s]\n", ctx->iformat->name, basepath); xasprintf(&m.resolution, "%dx%d", vc->width, vc->height); if( ctx->bit_rate > 8 ) m.bitrate = ctx->bit_rate / 8; if( ctx->duration > 0 ) { duration = (int)(ctx->duration / AV_TIME_BASE); hours = (int)(duration / 3600); min = (int)(duration / 60 % 60); sec = (int)(duration % 60); ms = (int)(ctx->duration / (AV_TIME_BASE/1000) % 1000); xasprintf(&m.duration, "%d:%02d:%02d.%03d", hours, min, sec, ms); } /* NOTE: The DLNA spec only provides for ASF (WMV), TS, PS, and MP4 containers. * Skip DLNA parsing for everything else. */ if( strcmp(ctx->iformat->name, "avi") == 0 ) { xasprintf(&m.mime, "video/x-msvideo"); if( vc->codec_id == AV_CODEC_ID_MPEG4 ) { fourcc[0] = vc->codec_tag & 0xff; fourcc[1] = vc->codec_tag>>8 & 0xff; fourcc[2] = vc->codec_tag>>16 & 0xff; fourcc[3] = vc->codec_tag>>24 & 0xff; if( memcmp(fourcc, "XVID", 4) == 0 || memcmp(fourcc, "DX50", 4) == 0 || memcmp(fourcc, "DIVX", 4) == 0 ) xasprintf(&m.creator, "DiVX"); } } else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 && ends_with(path, ".mov") ) xasprintf(&m.mime, "video/quicktime"); else if( strncmp(ctx->iformat->name, "matroska", 8) == 0 ) xasprintf(&m.mime, "video/x-matroska"); else if( strcmp(ctx->iformat->name, "flv") == 0 ) xasprintf(&m.mime, "video/x-flv"); if( m.mime ) goto video_no_dlna; switch( vc->codec_id ) { case AV_CODEC_ID_MPEG1VIDEO: if( strcmp(ctx->iformat->name, "mpeg") == 0 ) { if( (vc->width == 352) && (vc->height <= 288) ) { m.dlna_pn = strdup("MPEG1"); } xasprintf(&m.mime, "video/mpeg"); } break; case AV_CODEC_ID_MPEG2VIDEO: m.dlna_pn = malloc(64); off = sprintf(m.dlna_pn, "MPEG_"); if( strcmp(ctx->iformat->name, "mpegts") == 0 ) { int raw_packet_size; int dlna_ts_present = dlna_timestamp_is_present(path, &raw_packet_size); DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is %s MPEG2 TS packet size %d\n", video_stream, basepath, m.resolution, raw_packet_size); off += sprintf(m.dlna_pn+off, "TS_"); if( (vc->width >= 1280) && (vc->height >= 720) ) { off += sprintf(m.dlna_pn+off, "HD_NA"); } else { off += sprintf(m.dlna_pn+off, "SD_"); if( (vc->height == 576) || (vc->height == 288) ) off += sprintf(m.dlna_pn+off, "EU"); else off += sprintf(m.dlna_pn+off, "NA"); } if( raw_packet_size == MPEG_TS_PACKET_LENGTH_DLNA ) { if (dlna_ts_present) ts_timestamp = VALID; else ts_timestamp = EMPTY; } else if( raw_packet_size != MPEG_TS_PACKET_LENGTH ) { DPRINTF(E_DEBUG, L_METADATA, "Unsupported DLNA TS packet size [%d] (%s)\n", raw_packet_size, basepath); free(m.dlna_pn); m.dlna_pn = NULL; } switch( ts_timestamp ) { case NONE: xasprintf(&m.mime, "video/mpeg"); if( m.dlna_pn ) off += sprintf(m.dlna_pn+off, "_ISO"); break; case VALID: off += sprintf(m.dlna_pn+off, "_T"); case EMPTY: xasprintf(&m.mime, "video/vnd.dlna.mpeg-tts"); default: break; } } else if( strcmp(ctx->iformat->name, "mpeg") == 0 ) { DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is %s MPEG2 PS\n", video_stream, basepath, m.resolution); off += sprintf(m.dlna_pn+off, "PS_"); if( (vc->height == 576) || (vc->height == 288) ) off += sprintf(m.dlna_pn+off, "PAL"); else off += sprintf(m.dlna_pn+off, "NTSC"); xasprintf(&m.mime, "video/mpeg"); } else { DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s [%s] is %s non-DLNA MPEG2\n", video_stream, basepath, ctx->iformat->name, m.resolution); free(m.dlna_pn); m.dlna_pn = NULL; } break; case AV_CODEC_ID_H264: m.dlna_pn = malloc(128); off = sprintf(m.dlna_pn, "AVC_"); if( strcmp(ctx->iformat->name, "mpegts") == 0 ) { AVRational display_aspect_ratio; int fps, interlaced; int raw_packet_size; int dlna_ts_present = dlna_timestamp_is_present(path, &raw_packet_size); off += sprintf(m.dlna_pn+off, "TS_"); if (vc->sample_aspect_ratio.num) { av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, vc->width * vc->sample_aspect_ratio.num, vc->height * vc->sample_aspect_ratio.den, 1024*1024); } fps = lav_get_fps(ctx->streams[video_stream]); interlaced = lav_get_interlaced(vc, ctx->streams[video_stream]); if( ((((vc->width == 1920 || vc->width == 1440) && vc->height == 1080) || (vc->width == 720 && vc->height == 480)) && fps == 59 && interlaced) || ((vc->width == 1280 && vc->height == 720) && fps == 59 && !interlaced) ) { if( (vc->profile == FF_PROFILE_H264_MAIN || vc->profile == FF_PROFILE_H264_HIGH) && audio_profile == PROFILE_AUDIO_AC3 ) { off += sprintf(m.dlna_pn+off, "HD_60_"); vc->profile = FF_PROFILE_SKIP; } } else if( ((vc->width == 1920 && vc->height == 1080) || (vc->width == 1440 && vc->height == 1080) || (vc->width == 1280 && vc->height == 720) || (vc->width == 720 && vc->height == 576)) && interlaced && fps == 50 ) { if( (vc->profile == FF_PROFILE_H264_MAIN || vc->profile == FF_PROFILE_H264_HIGH) && audio_profile == PROFILE_AUDIO_AC3 ) { off += sprintf(m.dlna_pn+off, "HD_50_"); vc->profile = FF_PROFILE_SKIP; } } switch( vc->profile ) { case FF_PROFILE_H264_BASELINE: case FF_PROFILE_H264_CONSTRAINED_BASELINE: off += sprintf(m.dlna_pn+off, "BL_"); if( vc->width <= 352 && vc->height <= 288 && vc->bit_rate <= 384000 ) { off += sprintf(m.dlna_pn+off, "CIF15_"); break; } else if( vc->width <= 352 && vc->height <= 288 && vc->bit_rate <= 3000000 ) { off += sprintf(m.dlna_pn+off, "CIF30_"); break; } /* Fall back to Main Profile if it doesn't match a Baseline DLNA profile. */ else off -= 3; default: case FF_PROFILE_H264_MAIN: off += sprintf(m.dlna_pn+off, "MP_"); if( vc->profile != FF_PROFILE_H264_BASELINE && vc->profile != FF_PROFILE_H264_CONSTRAINED_BASELINE && vc->profile != FF_PROFILE_H264_MAIN ) { DPRINTF(E_DEBUG, L_METADATA, "Unknown AVC profile %d; assuming MP. [%s]\n", vc->profile, basepath); } if( vc->width <= 720 && vc->height <= 576 && vc->bit_rate <= 10000000 ) { off += sprintf(m.dlna_pn+off, "SD_"); } else if( vc->width <= 1920 && vc->height <= 1152 && vc->bit_rate <= 20000000 ) { off += sprintf(m.dlna_pn+off, "HD_"); } else { DPRINTF(E_DEBUG, L_METADATA, "Unsupported h.264 video profile! [%s, %dx%d, %dbps : %s]\n", m.dlna_pn, vc->width, vc->height, vc->bit_rate, basepath); free(m.dlna_pn); m.dlna_pn = NULL; } break; case FF_PROFILE_H264_HIGH: off += sprintf(m.dlna_pn+off, "HP_"); if( vc->width <= 1920 && vc->height <= 1152 && vc->bit_rate <= 30000000 && audio_profile == PROFILE_AUDIO_AC3 ) { off += sprintf(m.dlna_pn+off, "HD_"); } else { DPRINTF(E_DEBUG, L_METADATA, "Unsupported h.264 HP video profile! [%dbps, %d audio : %s]\n", vc->bit_rate, audio_profile, basepath); free(m.dlna_pn); m.dlna_pn = NULL; } break; case FF_PROFILE_SKIP: break; } if( !m.dlna_pn ) break; switch( audio_profile ) { case PROFILE_AUDIO_MP3: off += sprintf(m.dlna_pn+off, "MPEG1_L3"); break; case PROFILE_AUDIO_AC3: off += sprintf(m.dlna_pn+off, "AC3"); break; case PROFILE_AUDIO_AAC: case PROFILE_AUDIO_AAC_MULT5: off += sprintf(m.dlna_pn+off, "AAC_MULT5"); break; default: DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for %s file [%s]\n", m.dlna_pn, basepath); free(m.dlna_pn); m.dlna_pn = NULL; break; } if( !m.dlna_pn ) break; if( raw_packet_size == MPEG_TS_PACKET_LENGTH_DLNA ) { if( vc->profile == FF_PROFILE_H264_HIGH || dlna_ts_present ) ts_timestamp = VALID; else ts_timestamp = EMPTY; } else if( raw_packet_size != MPEG_TS_PACKET_LENGTH ) { DPRINTF(E_DEBUG, L_METADATA, "Unsupported DLNA TS packet size [%d] (%s)\n", raw_packet_size, basepath); free(m.dlna_pn); m.dlna_pn = NULL; } switch( ts_timestamp ) { case NONE: if( m.dlna_pn ) off += sprintf(m.dlna_pn+off, "_ISO"); break; case VALID: off += sprintf(m.dlna_pn+off, "_T"); case EMPTY: xasprintf(&m.mime, "video/vnd.dlna.mpeg-tts"); default: break; } } else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 ) { off += sprintf(m.dlna_pn+off, "MP4_"); switch( vc->profile ) { case FF_PROFILE_H264_BASELINE: case FF_PROFILE_H264_CONSTRAINED_BASELINE: if( vc->width <= 352 && vc->height <= 288 ) { if( ctx->bit_rate < 600000 ) off += sprintf(m.dlna_pn+off, "BL_CIF15_"); else if( ctx->bit_rate < 5000000 ) off += sprintf(m.dlna_pn+off, "BL_CIF30_"); else goto mp4_mp_fallback; if( audio_profile == PROFILE_AUDIO_AMR ) { off += sprintf(m.dlna_pn+off, "AMR"); } else if( audio_profile == PROFILE_AUDIO_AAC ) { off += sprintf(m.dlna_pn+off, "AAC_"); if( ctx->bit_rate < 520000 ) { off += sprintf(m.dlna_pn+off, "520"); } else if( ctx->bit_rate < 940000 ) { off += sprintf(m.dlna_pn+off, "940"); } else { off -= 13; goto mp4_mp_fallback; } } else { off -= 9; goto mp4_mp_fallback; } } else if( vc->width <= 720 && vc->height <= 576 ) { if( vc->level == 30 && audio_profile == PROFILE_AUDIO_AAC && ctx->bit_rate <= 5000000 ) off += sprintf(m.dlna_pn+off, "BL_L3L_SD_AAC"); else if( vc->level <= 31 && audio_profile == PROFILE_AUDIO_AAC && ctx->bit_rate <= 15000000 ) off += sprintf(m.dlna_pn+off, "BL_L31_HD_AAC"); else goto mp4_mp_fallback; } else if( vc->width <= 1280 && vc->height <= 720 ) { if( vc->level <= 31 && audio_profile == PROFILE_AUDIO_AAC && ctx->bit_rate <= 15000000 ) off += sprintf(m.dlna_pn+off, "BL_L31_HD_AAC"); else if( vc->level <= 32 && audio_profile == PROFILE_AUDIO_AAC && ctx->bit_rate <= 21000000 ) off += sprintf(m.dlna_pn+off, "BL_L32_HD_AAC"); else goto mp4_mp_fallback; } else goto mp4_mp_fallback; break; case FF_PROFILE_H264_MAIN: mp4_mp_fallback: off += sprintf(m.dlna_pn+off, "MP_"); /* AVC MP4 SD profiles - 10 Mbps max */ if( vc->width <= 720 && vc->height <= 576 && vc->bit_rate <= 10000000 ) { sprintf(m.dlna_pn+off, "SD_"); if( audio_profile == PROFILE_AUDIO_AC3 ) off += sprintf(m.dlna_pn+off, "AC3"); else if( audio_profile == PROFILE_AUDIO_AAC || audio_profile == PROFILE_AUDIO_AAC_MULT5 ) off += sprintf(m.dlna_pn+off, "AAC_MULT5"); else if( audio_profile == PROFILE_AUDIO_MP3 ) off += sprintf(m.dlna_pn+off, "MPEG1_L3"); else m.dlna_pn[10] = '\0'; } else if( vc->width <= 1280 && vc->height <= 720 && vc->bit_rate <= 15000000 && audio_profile == PROFILE_AUDIO_AAC ) { off += sprintf(m.dlna_pn+off, "HD_720p_AAC"); } else if( vc->width <= 1920 && vc->height <= 1080 && vc->bit_rate <= 21000000 && audio_profile == PROFILE_AUDIO_AAC ) { off += sprintf(m.dlna_pn+off, "HD_1080i_AAC"); } if( strlen(m.dlna_pn) <= 11 ) { DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for %s file %s\n", m.dlna_pn, basepath); free(m.dlna_pn); m.dlna_pn = NULL; } break; case FF_PROFILE_H264_HIGH: if( vc->width <= 1920 && vc->height <= 1080 && vc->bit_rate <= 25000000 && audio_profile == PROFILE_AUDIO_AAC ) { off += sprintf(m.dlna_pn+off, "HP_HD_AAC"); } break; default: DPRINTF(E_DEBUG, L_METADATA, "AVC profile [%d] not recognized for file %s\n", vc->profile, basepath); free(m.dlna_pn); m.dlna_pn = NULL; break; } } else { free(m.dlna_pn); m.dlna_pn = NULL; } DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is h.264\n", video_stream, basepath); break; case AV_CODEC_ID_MPEG4: fourcc[0] = vc->codec_tag & 0xff; fourcc[1] = vc->codec_tag>>8 & 0xff; fourcc[2] = vc->codec_tag>>16 & 0xff; fourcc[3] = vc->codec_tag>>24 & 0xff; DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is MPEG4 [%c%c%c%c/0x%X]\n", video_stream, basepath, isprint(fourcc[0]) ? fourcc[0] : '_', isprint(fourcc[1]) ? fourcc[1] : '_', isprint(fourcc[2]) ? fourcc[2] : '_', isprint(fourcc[3]) ? fourcc[3] : '_', vc->codec_tag); if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 ) { m.dlna_pn = malloc(128); off = sprintf(m.dlna_pn, "MPEG4_P2_"); if( ends_with(path, ".3gp") ) { xasprintf(&m.mime, "video/3gpp"); switch( audio_profile ) { case PROFILE_AUDIO_AAC: off += sprintf(m.dlna_pn+off, "3GPP_SP_L0B_AAC"); break; case PROFILE_AUDIO_AMR: off += sprintf(m.dlna_pn+off, "3GPP_SP_L0B_AMR"); break; default: DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for MPEG4-P2 3GP/0x%X file %s\n", ac->codec_id, basepath); free(m.dlna_pn); m.dlna_pn = NULL; break; } } else { if( ctx->bit_rate <= 1000000 && audio_profile == PROFILE_AUDIO_AAC ) { off += sprintf(m.dlna_pn+off, "MP4_ASP_AAC"); } else if( ctx->bit_rate <= 4000000 && vc->width <= 640 && vc->height <= 480 && audio_profile == PROFILE_AUDIO_AAC ) { off += sprintf(m.dlna_pn+off, "MP4_SP_VGA_AAC"); } else { DPRINTF(E_DEBUG, L_METADATA, "Unsupported h.264 video profile! [%dx%d, %dbps]\n", vc->width, vc->height, ctx->bit_rate); free(m.dlna_pn); m.dlna_pn = NULL; } } } break; case AV_CODEC_ID_WMV3: /* I'm not 100% sure this is correct, but it works on everything I could get my hands on */ if( vc->extradata_size > 0 ) { if( !((vc->extradata[0] >> 3) & 1) ) vc->level = 0; if( !((vc->extradata[0] >> 6) & 1) ) vc->profile = 0; } case AV_CODEC_ID_VC1: if( strcmp(ctx->iformat->name, "asf") != 0 ) { DPRINTF(E_DEBUG, L_METADATA, "Skipping DLNA parsing for non-ASF VC1 file %s\n", path); break; } m.dlna_pn = malloc(64); off = sprintf(m.dlna_pn, "WMV"); DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is VC1\n", video_stream, basepath); xasprintf(&m.mime, "video/x-ms-wmv"); if( (vc->width <= 176) && (vc->height <= 144) && (vc->level == 0) ) { off += sprintf(m.dlna_pn+off, "SPLL_"); switch( audio_profile ) { case PROFILE_AUDIO_MP3: off += sprintf(m.dlna_pn+off, "MP3"); break; case PROFILE_AUDIO_WMA_BASE: off += sprintf(m.dlna_pn+off, "BASE"); break; default: DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for WMVSPLL/0x%X file %s\n", audio_profile, basepath); free(m.dlna_pn); m.dlna_pn = NULL; break; } } else if( (vc->width <= 352) && (vc->height <= 288) && (vc->profile == 0) && (ctx->bit_rate/8 <= 384000) ) { off += sprintf(m.dlna_pn+off, "SPML_"); switch( audio_profile ) { case PROFILE_AUDIO_MP3: off += sprintf(m.dlna_pn+off, "MP3"); break; case PROFILE_AUDIO_WMA_BASE: off += sprintf(m.dlna_pn+off, "BASE"); break; default: DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for WMVSPML/0x%X file %s\n", audio_profile, basepath); free(m.dlna_pn); m.dlna_pn = NULL; break; } } else if( (vc->width <= 720) && (vc->height <= 576) && (ctx->bit_rate/8 <= 10000000) ) { off += sprintf(m.dlna_pn+off, "MED_"); switch( audio_profile ) { case PROFILE_AUDIO_WMA_PRO: off += sprintf(m.dlna_pn+off, "PRO"); break; case PROFILE_AUDIO_WMA_FULL: off += sprintf(m.dlna_pn+off, "FULL"); break; case PROFILE_AUDIO_WMA_BASE: off += sprintf(m.dlna_pn+off, "BASE"); break; default: DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for WMVMED/0x%X file %s\n", audio_profile, basepath); free(m.dlna_pn); m.dlna_pn = NULL; break; } } else if( (vc->width <= 1920) && (vc->height <= 1080) && (ctx->bit_rate/8 <= 20000000) ) { off += sprintf(m.dlna_pn+off, "HIGH_"); switch( audio_profile ) { case PROFILE_AUDIO_WMA_PRO: off += sprintf(m.dlna_pn+off, "PRO"); break; case PROFILE_AUDIO_WMA_FULL: off += sprintf(m.dlna_pn+off, "FULL"); break; default: DPRINTF(E_DEBUG, L_METADATA, "No DLNA profile found for WMVHIGH/0x%X file %s\n", audio_profile, basepath); free(m.dlna_pn); m.dlna_pn = NULL; break; } } break; case AV_CODEC_ID_MSMPEG4V3: xasprintf(&m.mime, "video/x-msvideo"); default: DPRINTF(E_DEBUG, L_METADATA, "Stream %d of %s is %s [type %d]\n", video_stream, basepath, m.resolution, vc->codec_id); break; } } if( strcmp(ctx->iformat->name, "asf") == 0 ) { if( readtags((char *)path, &video, &file, "en_US", "asf") == 0 ) { if( video.title && *video.title ) { m.title = escape_tag(trim(video.title), 1); } if( video.genre && *video.genre ) { m.genre = escape_tag(trim(video.genre), 1); } if( video.contributor[ROLE_TRACKARTIST] && *video.contributor[ROLE_TRACKARTIST] ) { m.artist = escape_tag(trim(video.contributor[ROLE_TRACKARTIST]), 1); } if( video.contributor[ROLE_ALBUMARTIST] && *video.contributor[ROLE_ALBUMARTIST] ) { m.creator = escape_tag(trim(video.contributor[ROLE_ALBUMARTIST]), 1); } else { m.creator = m.artist; free_flags &= ~FLAG_CREATOR; } if (!m.thumb_data) { m.thumb_data = video.image; m.thumb_size = video.image_size; } } } #ifndef NETGEAR #if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 ) { if( ctx->metadata ) { AVDictionaryEntry *tag = NULL; //DEBUG DPRINTF(E_DEBUG, L_METADATA, "Metadata:\n"); while( (tag = av_dict_get(ctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)) ) { //DEBUG DPRINTF(E_DEBUG, L_METADATA, " %-16s: %s\n", tag->key, tag->value); if( strcmp(tag->key, "title") == 0 ) m.title = escape_tag(trim(tag->value), 1); else if( strcmp(tag->key, "genre") == 0 ) m.genre = escape_tag(trim(tag->value), 1); else if( strcmp(tag->key, "artist") == 0 ) m.artist = escape_tag(trim(tag->value), 1); else if( strcmp(tag->key, "comment") == 0 ) m.comment = escape_tag(trim(tag->value), 1); } } } #endif #endif video_no_dlna: #ifdef TIVO_SUPPORT if( ends_with(path, ".TiVo") && is_tivo_file(path) ) { if( m.dlna_pn ) { free(m.dlna_pn); m.dlna_pn = NULL; } m.mime = realloc(m.mime, 21); strcpy(m.mime, "video/x-tivo-mpeg"); } #endif strcpy(nfo, path); ext = strrchr(nfo, '.'); if( ext ) { strcpy(ext+1, "nfo"); if( access(nfo, F_OK) == 0 ) { parse_nfo(nfo, &m); } } if( !m.mime ) { if( strcmp(ctx->iformat->name, "avi") == 0 ) xasprintf(&m.mime, "video/x-msvideo"); else if( strncmp(ctx->iformat->name, "mpeg", 4) == 0 ) xasprintf(&m.mime, "video/mpeg"); else if( strcmp(ctx->iformat->name, "asf") == 0 ) xasprintf(&m.mime, "video/x-ms-wmv"); else if( strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 ) if( ends_with(path, ".mov") ) xasprintf(&m.mime, "video/quicktime"); else xasprintf(&m.mime, "video/mp4"); else if( strncmp(ctx->iformat->name, "matroska", 8) == 0 ) xasprintf(&m.mime, "video/x-matroska"); else if( strcmp(ctx->iformat->name, "flv") == 0 ) xasprintf(&m.mime, "video/x-flv"); else DPRINTF(E_WARN, L_METADATA, "%s: Unhandled format: %s\n", path, ctx->iformat->name); } if( !m.date ) { m.date = malloc(20); modtime = localtime(&file.st_mtime); strftime(m.date, 20, "%FT%T", modtime); } if( !m.title ) m.title = strdup(name); album_art = find_album_art(path, m.thumb_data, m.thumb_size); freetags(&video); lav_close(ctx); ret = sql_exec(db, "INSERT into DETAILS" " (PATH, SIZE, TIMESTAMP, DURATION, DATE, CHANNELS, BITRATE, SAMPLERATE, RESOLUTION," " TITLE, CREATOR, ARTIST, GENRE, COMMENT, DLNA_PN, MIME, ALBUM_ART) " "VALUES" " (%Q, %lld, %lld, %Q, %Q, %u, %u, %u, %Q, '%q', %Q, %Q, %Q, %Q, %Q, '%q', %lld);", path, (long long)file.st_size, (long long)file.st_mtime, m.duration, m.date, m.channels, m.bitrate, m.frequency, m.resolution, m.title, m.creator, m.artist, m.genre, m.comment, m.dlna_pn, m.mime, album_art); if( ret != SQLITE_OK ) { DPRINTF(E_ERROR, L_METADATA, "Error inserting details for '%s'!\n", path); ret = 0; } else { ret = sqlite3_last_insert_rowid(db); check_for_captions(path, ret); } free_metadata(&m, free_flags); free(path_cpy); return ret; } minidlna-1.1.5+dfsg/metadata.h000066400000000000000000000061071261774340000161710ustar00rootroot00000000000000/* Metadata extraction * * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Justin Maggard * * MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #ifndef __METADATA_H__ #define __METADATA_H__ typedef struct metadata_s { char * title; char * artist; char * creator; char * album; char * genre; char * comment; unsigned int disc; unsigned int track; unsigned int channels; unsigned int bitrate; unsigned int frequency; unsigned int rotation; char * resolution; char * duration; char * date; char * mime; char * dlna_pn; int thumb_size; uint8_t * thumb_data; } metadata_t; typedef enum { AAC_INVALID = 0, AAC_MAIN = 1, /* AAC Main */ AAC_LC = 2, /* AAC Low complexity */ AAC_SSR = 3, /* AAC SSR */ AAC_LTP = 4, /* AAC Long term prediction */ AAC_HE = 5, /* AAC High efficiency (SBR) */ AAC_SCALE = 6, /* Scalable */ AAC_TWINVQ = 7, /* TwinVQ */ AAC_CELP = 8, /* CELP */ AAC_HVXC = 9, /* HVXC */ AAC_TTSI = 12, /* TTSI */ AAC_MS = 13, /* Main synthetic */ AAC_WAVE = 14, /* Wavetable synthesis */ AAC_MIDI = 15, /* General MIDI */ AAC_FX = 16, /* Algorithmic Synthesis and Audio FX */ AAC_LC_ER = 17, /* AAC Low complexity with error recovery */ AAC_LTP_ER = 19, /* AAC Long term prediction with error recovery */ AAC_SCALE_ER = 20, /* AAC scalable with error recovery */ AAC_TWINVQ_ER = 21, /* TwinVQ with error recovery */ AAC_BSAC_ER = 22, /* BSAC with error recovery */ AAC_LD_ER = 23, /* AAC LD with error recovery */ AAC_CELP_ER = 24, /* CELP with error recovery */ AAC_HXVC_ER = 25, /* HXVC with error recovery */ AAC_HILN_ER = 26, /* HILN with error recovery */ AAC_PARAM_ER = 27, /* Parametric with error recovery */ AAC_SSC = 28, /* AAC SSC */ AAC_HE_L3 = 31, /* Reserved : seems to be HeAAC L3 */ } aac_object_type_t; typedef enum { NONE, EMPTY, VALID } ts_timestamp_t; int ends_with(const char *haystack, const char *needle); void check_for_captions(const char *path, int64_t detailID); int64_t GetFolderMetadata(const char *name, const char *path, const char *artist, const char *genre, int64_t album_art); int64_t GetAudioMetadata(const char *path, char *name); int64_t GetImageMetadata(const char *path, char *name); int64_t GetVideoMetadata(const char *path, char *name); #endif minidlna-1.1.5+dfsg/minidlna.c000066400000000000000000001057051261774340000162030ustar00rootroot00000000000000/* MiniDLNA project * * http://sourceforge.net/projects/minidlna/ * * MiniDLNA media server * Copyright (C) 2008-2012 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . * * Portions of the code from the MiniUPnP project: * * Copyright (c) 2006-2007, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #ifdef ENABLE_NLS #include #include #endif #include "upnpglobalvars.h" #include "sql.h" #include "upnphttp.h" #include "upnpdescgen.h" #include "minidlnapath.h" #include "getifaddr.h" #include "upnpsoap.h" #include "options.h" #include "utils.h" #include "minissdp.h" #include "minidlnatypes.h" #include "process.h" #include "upnpevents.h" #include "scanner.h" #include "inotify.h" #include "log.h" #include "tivo_beacon.h" #include "tivo_utils.h" #if SQLITE_VERSION_NUMBER < 3005001 # warning "Your SQLite3 library appears to be too old! Please use 3.5.1 or newer." # define sqlite3_threadsafe() 0 #endif /* OpenAndConfHTTPSocket() : * setup the socket used to handle incoming HTTP connections. */ static int OpenAndConfHTTPSocket(unsigned short port) { int s; int i = 1; struct sockaddr_in listenname; /* Initialize client type cache */ memset(&clients, 0, sizeof(struct client_cache_s)); s = socket(PF_INET, SOCK_STREAM, 0); if (s < 0) { DPRINTF(E_ERROR, L_GENERAL, "socket(http): %s\n", strerror(errno)); return -1; } if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) DPRINTF(E_WARN, L_GENERAL, "setsockopt(http, SO_REUSEADDR): %s\n", strerror(errno)); memset(&listenname, 0, sizeof(struct sockaddr_in)); listenname.sin_family = AF_INET; listenname.sin_port = htons(port); listenname.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(s, (struct sockaddr *)&listenname, sizeof(struct sockaddr_in)) < 0) { DPRINTF(E_ERROR, L_GENERAL, "bind(http): %s\n", strerror(errno)); close(s); return -1; } if (listen(s, 6) < 0) { DPRINTF(E_ERROR, L_GENERAL, "listen(http): %s\n", strerror(errno)); close(s); return -1; } return s; } /* Handler for the SIGTERM signal (kill) * SIGINT is also handled */ static void sigterm(int sig) { signal(sig, SIG_IGN); /* Ignore this signal while we are quitting */ DPRINTF(E_WARN, L_GENERAL, "received signal %d, good-bye\n", sig); quitting = 1; } static void sigusr1(int sig) { signal(sig, sigusr1); DPRINTF(E_WARN, L_GENERAL, "received signal %d, clear cache\n", sig); memset(&clients, '\0', sizeof(clients)); } static void sighup(int sig) { signal(sig, sighup); DPRINTF(E_WARN, L_GENERAL, "received signal %d, re-read\n", sig); reload_ifaces(1); } /* record the startup time */ static void set_startup_time(void) { startup_time = time(NULL); } static void getfriendlyname(char *buf, int len) { char *p = NULL; char hn[256]; int off; if (gethostname(hn, sizeof(hn)) == 0) { strncpyt(buf, hn, len); p = strchr(buf, '.'); if (p) *p = '\0'; } else strcpy(buf, "Unknown"); off = strlen(buf); off += snprintf(buf+off, len-off, ": "); #ifdef READYNAS FILE *info; char ibuf[64], *key, *val; snprintf(buf+off, len-off, "ReadyNAS"); info = fopen("/proc/sys/dev/boot/info", "r"); if (!info) return; while ((val = fgets(ibuf, 64, info)) != NULL) { key = strsep(&val, ": \t"); val = trim(val); if (strcmp(key, "model") == 0) { snprintf(buf+off, len-off, "%s", val); key = strchr(val, ' '); if (key) { strncpyt(modelnumber, key+1, MODELNUMBER_MAX_LEN); *key = '\0'; } snprintf(modelname, MODELNAME_MAX_LEN, "Windows Media Connect compatible (%s)", val); } else if (strcmp(key, "serial") == 0) { strncpyt(serialnumber, val, SERIALNUMBER_MAX_LEN); if (serialnumber[0] == '\0') { char mac_str[13]; if (getsyshwaddr(mac_str, sizeof(mac_str)) == 0) strcpy(serialnumber, mac_str); else strcpy(serialnumber, "0"); } break; } } fclose(info); #if PNPX memcpy(pnpx_hwid+4, "01F2", 4); if (strcmp(modelnumber, "NVX") == 0) memcpy(pnpx_hwid+17, "0101", 4); else if (strcmp(modelnumber, "Pro") == 0 || strcmp(modelnumber, "Pro 6") == 0 || strncmp(modelnumber, "Ultra 6", 7) == 0) memcpy(pnpx_hwid+17, "0102", 4); else if (strcmp(modelnumber, "Pro 2") == 0 || strncmp(modelnumber, "Ultra 2", 7) == 0) memcpy(pnpx_hwid+17, "0103", 4); else if (strcmp(modelnumber, "Pro 4") == 0 || strncmp(modelnumber, "Ultra 4", 7) == 0) memcpy(pnpx_hwid+17, "0104", 4); else if (strcmp(modelnumber+1, "100") == 0) memcpy(pnpx_hwid+17, "0105", 4); else if (strcmp(modelnumber+1, "200") == 0) memcpy(pnpx_hwid+17, "0106", 4); /* 0107 = Stora */ else if (strcmp(modelnumber, "Duo v2") == 0) memcpy(pnpx_hwid+17, "0108", 4); else if (strcmp(modelnumber, "NV+ v2") == 0) memcpy(pnpx_hwid+17, "0109", 4); #endif #else char * logname; logname = getenv("LOGNAME"); #ifndef STATIC // Disable for static linking if (!logname) { struct passwd * pwent; pwent = getpwuid(getuid()); if (pwent) logname = pwent->pw_name; } #endif snprintf(buf+off, len-off, "%s", logname?logname:"Unknown"); #endif } static int open_db(sqlite3 **sq3) { char path[PATH_MAX]; int new_db = 0; snprintf(path, sizeof(path), "%s/files.db", db_path); if (access(path, F_OK) != 0) { new_db = 1; make_dir(db_path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); } if (sqlite3_open(path, &db) != SQLITE_OK) DPRINTF(E_FATAL, L_GENERAL, "ERROR: Failed to open sqlite database! Exiting...\n"); if (sq3) *sq3 = db; sqlite3_busy_timeout(db, 5000); sql_exec(db, "pragma page_size = 4096"); sql_exec(db, "pragma journal_mode = OFF"); sql_exec(db, "pragma synchronous = OFF;"); sql_exec(db, "pragma default_cache_size = 8192;"); return new_db; } static void check_db(sqlite3 *db, int new_db, pid_t *scanner_pid) { struct media_dir_s *media_path = NULL; char cmd[PATH_MAX*2]; char **result; int i, rows = 0; int ret; if (!new_db) { /* Check if any new media dirs appeared */ media_path = media_dirs; while (media_path) { ret = sql_get_int_field(db, "SELECT TIMESTAMP from DETAILS where PATH = %Q", media_path->path); if (ret != media_path->types) { ret = 1; goto rescan; } media_path = media_path->next; } /* Check if any media dirs disappeared */ sql_get_table(db, "SELECT VALUE from SETTINGS where KEY = 'media_dir'", &result, &rows, NULL); for (i=1; i <= rows; i++) { media_path = media_dirs; while (media_path) { if (strcmp(result[i], media_path->path) == 0) break; media_path = media_path->next; } if (!media_path) { ret = 2; sqlite3_free_table(result); goto rescan; } } sqlite3_free_table(result); } ret = db_upgrade(db); if (ret != 0) { rescan: if (ret < 0) DPRINTF(E_WARN, L_GENERAL, "Creating new database at %s/files.db\n", db_path); else if (ret == 1) DPRINTF(E_WARN, L_GENERAL, "New media_dir detected; rescanning...\n"); else if (ret == 2) DPRINTF(E_WARN, L_GENERAL, "Removed media_dir detected; rescanning...\n"); else DPRINTF(E_WARN, L_GENERAL, "Database version mismatch (%d=>%d); need to recreate...\n", ret, DB_VERSION); sqlite3_close(db); snprintf(cmd, sizeof(cmd), "rm -rf %s/files.db %s/art_cache", db_path, db_path); if (system(cmd) != 0) DPRINTF(E_FATAL, L_GENERAL, "Failed to clean old file cache! Exiting...\n"); open_db(&db); if (CreateDatabase() != 0) DPRINTF(E_FATAL, L_GENERAL, "ERROR: Failed to create sqlite database! Exiting...\n"); #if USE_FORK scanning = 1; sqlite3_close(db); *scanner_pid = fork(); open_db(&db); if (*scanner_pid == 0) /* child (scanner) process */ { start_scanner(); sqlite3_close(db); log_close(); freeoptions(); free(children); exit(EXIT_SUCCESS); } else if (*scanner_pid < 0) { start_scanner(); } #else start_scanner(); #endif } } static int writepidfile(const char *fname, int pid, uid_t uid) { FILE *pidfile; struct stat st; char path[PATH_MAX], *dir; int ret = 0; if(!fname || *fname == '\0') return -1; /* Create parent directory if it doesn't already exist */ strncpyt(path, fname, sizeof(path)); dir = dirname(path); if (stat(dir, &st) == 0) { if (!S_ISDIR(st.st_mode)) { DPRINTF(E_ERROR, L_GENERAL, "Pidfile path is not a directory: %s\n", fname); return -1; } } else { if (make_dir(dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) != 0) { DPRINTF(E_ERROR, L_GENERAL, "Unable to create pidfile directory: %s\n", fname); return -1; } if (uid > 0) { if (chown(dir, uid, -1) != 0) DPRINTF(E_WARN, L_GENERAL, "Unable to change pidfile %s ownership: %s\n", dir, strerror(errno)); } } pidfile = fopen(fname, "w"); if (!pidfile) { DPRINTF(E_ERROR, L_GENERAL, "Unable to open pidfile for writing %s: %s\n", fname, strerror(errno)); return -1; } if (fprintf(pidfile, "%d\n", pid) <= 0) { DPRINTF(E_ERROR, L_GENERAL, "Unable to write to pidfile %s: %s\n", fname, strerror(errno)); ret = -1; } if (uid > 0) { if (fchown(fileno(pidfile), uid, -1) != 0) DPRINTF(E_WARN, L_GENERAL, "Unable to change pidfile %s ownership: %s\n", fname, strerror(errno)); } fclose(pidfile); return ret; } static int strtobool(const char *str) { return ((strcasecmp(str, "yes") == 0) || (strcasecmp(str, "true") == 0) || (atoi(str) == 1)); } static void init_nls(void) { #ifdef ENABLE_NLS setlocale(LC_MESSAGES, ""); setlocale(LC_CTYPE, "en_US.utf8"); DPRINTF(E_DEBUG, L_GENERAL, "Using locale dir %s\n", bindtextdomain("minidlna", getenv("TEXTDOMAINDIR"))); textdomain("minidlna"); #endif } /* init phase : * 1) read configuration file * 2) read command line arguments * 3) daemonize * 4) check and write pid file * 5) set startup time stamp * 6) compute presentation URL * 7) set signal handlers */ static int init(int argc, char **argv) { int i; int pid; int debug_flag = 0; int verbose_flag = 0; int options_flag = 0; struct sigaction sa; const char * presurl = NULL; const char * optionsfile = "/etc/minidlna.conf"; char mac_str[13]; char *string, *word; char *path; char buf[PATH_MAX]; char log_str[75] = "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn"; char *log_level = NULL; struct media_dir_s *media_dir; int ifaces = 0; media_types types; uid_t uid = 0; /* first check if "-f" option is used */ for (i=2; i= MAX_LAN_ADDR) { DPRINTF(E_ERROR, L_GENERAL, "Too many interfaces (max: %d), ignoring %s\n", MAX_LAN_ADDR, word); break; } while (isspace(*word)) word++; runtime_vars.ifaces[ifaces++] = word; } break; case UPNPPORT: runtime_vars.port = atoi(ary_options[i].value); break; case UPNPPRESENTATIONURL: presurl = ary_options[i].value; break; case UPNPNOTIFY_INTERVAL: runtime_vars.notify_interval = atoi(ary_options[i].value); break; case UPNPSERIAL: strncpyt(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN); break; case UPNPMODEL_NAME: strncpyt(modelname, ary_options[i].value, MODELNAME_MAX_LEN); break; case UPNPMODEL_NUMBER: strncpyt(modelnumber, ary_options[i].value, MODELNUMBER_MAX_LEN); break; case UPNPFRIENDLYNAME: strncpyt(friendly_name, ary_options[i].value, FRIENDLYNAME_MAX_LEN); break; case UPNPMEDIADIR: types = ALL_MEDIA; path = ary_options[i].value; word = strchr(path, ','); if (word && (access(path, F_OK) != 0)) { types = 0; while (*path) { if (*path == ',') { path++; break; } else if (*path == 'A' || *path == 'a') types |= TYPE_AUDIO; else if (*path == 'V' || *path == 'v') types |= TYPE_VIDEO; else if (*path == 'P' || *path == 'p') types |= TYPE_IMAGES; else DPRINTF(E_FATAL, L_GENERAL, "Media directory entry not understood [%s]\n", ary_options[i].value); path++; } } path = realpath(path, buf); if (!path || access(path, F_OK) != 0) { DPRINTF(E_ERROR, L_GENERAL, "Media directory \"%s\" not accessible [%s]\n", ary_options[i].value, strerror(errno)); break; } media_dir = calloc(1, sizeof(struct media_dir_s)); media_dir->path = strdup(path); media_dir->types = types; if (media_dirs) { struct media_dir_s *all_dirs = media_dirs; while( all_dirs->next ) all_dirs = all_dirs->next; all_dirs->next = media_dir; } else media_dirs = media_dir; break; case UPNPALBUMART_NAMES: for (string = ary_options[i].value; (word = strtok(string, "/")); string = NULL) { struct album_art_name_s * this_name = calloc(1, sizeof(struct album_art_name_s)); int len = strlen(word); if (word[len-1] == '*') { word[len-1] = '\0'; this_name->wildcard = 1; } this_name->name = strdup(word); if (album_art_names) { struct album_art_name_s * all_names = album_art_names; while( all_names->next ) all_names = all_names->next; all_names->next = this_name; } else album_art_names = this_name; } break; case UPNPDBDIR: path = realpath(ary_options[i].value, buf); if (!path) path = (ary_options[i].value); make_dir(path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); if (access(path, F_OK) != 0) DPRINTF(E_FATAL, L_GENERAL, "Database path not accessible! [%s]\n", path); strncpyt(db_path, path, PATH_MAX); break; case UPNPLOGDIR: path = realpath(ary_options[i].value, buf); if (!path) path = (ary_options[i].value); make_dir(path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); if (access(path, F_OK) != 0) DPRINTF(E_FATAL, L_GENERAL, "Log path not accessible! [%s]\n", path); strncpyt(log_path, path, PATH_MAX); break; case UPNPLOGLEVEL: log_level = ary_options[i].value; break; case UPNPINOTIFY: if (!strtobool(ary_options[i].value)) CLEARFLAG(INOTIFY_MASK); break; case ENABLE_TIVO: if (strtobool(ary_options[i].value)) SETFLAG(TIVO_MASK); break; case ENABLE_DLNA_STRICT: if (strtobool(ary_options[i].value)) SETFLAG(DLNA_STRICT_MASK); break; case ROOT_CONTAINER: switch (ary_options[i].value[0]) { case '.': runtime_vars.root_container = NULL; break; case 'B': case 'b': runtime_vars.root_container = BROWSEDIR_ID; break; case 'M': case 'm': runtime_vars.root_container = MUSIC_ID; break; case 'V': case 'v': runtime_vars.root_container = VIDEO_ID; break; case 'P': case 'p': runtime_vars.root_container = IMAGE_ID; break; default: runtime_vars.root_container = ary_options[i].value; DPRINTF(E_WARN, L_GENERAL, "Using arbitrary root container [%s]\n", ary_options[i].value); break; } break; case UPNPMINISSDPDSOCKET: minissdpdsocketpath = ary_options[i].value; break; case UPNPUUID: strcpy(uuidvalue+5, ary_options[i].value); break; case USER_ACCOUNT: uid = strtoul(ary_options[i].value, &string, 0); if (*string) { /* Symbolic username given, not UID. */ struct passwd *entry = getpwnam(ary_options[i].value); if (!entry) DPRINTF(E_FATAL, L_GENERAL, "Bad user '%s'.\n", ary_options[i].value); uid = entry->pw_uid; } break; case FORCE_SORT_CRITERIA: force_sort_criteria = ary_options[i].value; break; case MAX_CONNECTIONS: runtime_vars.max_connections = atoi(ary_options[i].value); break; case MERGE_MEDIA_DIRS: if (strtobool(ary_options[i].value)) SETFLAG(MERGE_MEDIA_DIRS_MASK); break; default: DPRINTF(E_ERROR, L_GENERAL, "Unknown option in file %s\n", optionsfile); } } if (log_path[0] == '\0') { if (db_path[0] == '\0') strncpyt(log_path, DEFAULT_LOG_PATH, PATH_MAX); else strncpyt(log_path, db_path, PATH_MAX); } if (db_path[0] == '\0') strncpyt(db_path, DEFAULT_DB_PATH, PATH_MAX); /* command line arguments processing */ for (i=1; i= MAX_LAN_ADDR) { DPRINTF(E_ERROR, L_GENERAL, "Too many interfaces (max: %d), ignoring %s\n", MAX_LAN_ADDR, argv[i]); break; } runtime_vars.ifaces[ifaces++] = argv[i]; } else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'f': i++; /* discarding, the config file is already read */ break; case 'h': runtime_vars.port = -1; // triggers help display break; case 'R': snprintf(buf, sizeof(buf), "rm -rf %s/files.db %s/art_cache", db_path, db_path); if (system(buf) != 0) DPRINTF(E_FATAL, L_GENERAL, "Failed to clean old file cache. EXITING\n"); break; case 'u': if (i+1 != argc) { i++; uid = strtoul(argv[i], &string, 0); if (*string) { /* Symbolic username given, not UID. */ struct passwd *entry = getpwnam(argv[i]); if (!entry) DPRINTF(E_FATAL, L_GENERAL, "Bad user '%s'.\n", argv[i]); uid = entry->pw_uid; } } else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; break; #ifdef __linux__ case 'S': SETFLAG(SYSTEMD_MASK); break; #endif case 'V': printf("Version " MINIDLNA_VERSION "\n"); exit(0); break; default: DPRINTF(E_ERROR, L_GENERAL, "Unknown option: %s\n", argv[i]); runtime_vars.port = -1; // triggers help display } } if (runtime_vars.port <= 0) { printf("Usage:\n\t" "%s [-d] [-v] [-f config_file] [-p port]\n" "\t\t[-i network_interface] [-u uid_to_run_as]\n" "\t\t[-t notify_interval] [-P pid_filename]\n" "\t\t[-s serial] [-m model_number]\n" #ifdef __linux__ "\t\t[-w url] [-R] [-L] [-S] [-V] [-h]\n" #else "\t\t[-w url] [-R] [-L] [-V] [-h]\n" #endif "\nNotes:\n\tNotify interval is in seconds. Default is 895 seconds.\n" "\tDefault pid file is %s.\n" "\tWith -d minidlna will run in debug mode (not daemonize).\n" "\t-w sets the presentation url. Default is http address on port 80\n" "\t-v enables verbose output\n" "\t-h displays this text\n" "\t-R forces a full rescan\n" "\t-L do not create playlists\n" #ifdef __linux__ "\t-S changes behaviour for systemd\n" #endif "\t-V print the version number\n", argv[0], pidfilename); return 1; } if (verbose_flag) { strcpy(log_str+65, "debug"); log_level = log_str; } else if (!log_level) log_level = log_str; /* Set the default log file path to NULL (stdout) */ path = NULL; if (debug_flag) { pid = getpid(); strcpy(log_str+65, "maxdebug"); log_level = log_str; } else if (GETFLAG(SYSTEMD_MASK)) { pid = getpid(); } else { pid = process_daemonize(); #ifdef READYNAS unlink("/ramfs/.upnp-av_scan"); path = "/var/log/upnp-av.log"; #else if (access(db_path, F_OK) != 0) make_dir(db_path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); snprintf(buf, sizeof(buf), "%s/minidlna.log", log_path); path = buf; #endif } log_init(path, log_level); if (process_check_if_running(pidfilename) < 0) { DPRINTF(E_ERROR, L_GENERAL, SERVER_NAME " is already running. EXITING.\n"); return 1; } set_startup_time(); /* presentation url */ if (presurl) strncpyt(presentationurl, presurl, PRESENTATIONURL_MAX_LEN); else strcpy(presentationurl, "/"); /* set signal handlers */ memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = sigterm; if (sigaction(SIGTERM, &sa, NULL)) DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGTERM"); if (sigaction(SIGINT, &sa, NULL)) DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGINT"); if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGPIPE"); if (signal(SIGHUP, &sighup) == SIG_ERR) DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGHUP"); signal(SIGUSR1, &sigusr1); sa.sa_handler = process_handle_child_termination; if (sigaction(SIGCHLD, &sa, NULL)) DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGCHLD"); if (writepidfile(pidfilename, pid, uid) != 0) pidfilename = NULL; if (uid > 0) { struct stat st; if (stat(db_path, &st) == 0 && st.st_uid != uid && chown(db_path, uid, -1) != 0) DPRINTF(E_ERROR, L_GENERAL, "Unable to set db_path [%s] ownership to %d: %s\n", db_path, uid, strerror(errno)); } if (uid > 0 && setuid(uid) == -1) DPRINTF(E_FATAL, L_GENERAL, "Failed to switch to uid '%d'. [%s] EXITING.\n", uid, strerror(errno)); children = calloc(runtime_vars.max_connections, sizeof(struct child)); if (!children) { DPRINTF(E_ERROR, L_GENERAL, "Allocation failed\n"); return 1; } return 0; } /* === main === */ /* process HTTP or SSDP requests */ int main(int argc, char **argv) { int ret, i; int shttpl = -1; int smonitor = -1; LIST_HEAD(httplisthead, upnphttp) upnphttphead; struct upnphttp * e = 0; struct upnphttp * next; fd_set readset; /* for select() */ fd_set writeset; struct timeval timeout, timeofday, lastnotifytime = {0, 0}; time_t lastupdatetime = 0; int max_fd = -1; int last_changecnt = 0; pid_t scanner_pid = 0; pthread_t inotify_thread = 0; #ifdef TIVO_SUPPORT uint8_t beacon_interval = 5; int sbeacon = -1; struct sockaddr_in tivo_bcast; struct timeval lastbeacontime = {0, 0}; #endif for (i = 0; i < L_MAX; i++) log_level[i] = E_WARN; init_nls(); ret = init(argc, argv); if (ret != 0) return 1; DPRINTF(E_WARN, L_GENERAL, "Starting " SERVER_NAME " version " MINIDLNA_VERSION ".\n"); if (sqlite3_libversion_number() < 3005001) { DPRINTF(E_WARN, L_GENERAL, "SQLite library is old. Please use version 3.5.1 or newer.\n"); } LIST_INIT(&upnphttphead); ret = open_db(NULL); if (ret == 0) { updateID = sql_get_int_field(db, "SELECT VALUE from SETTINGS where KEY = 'UPDATE_ID'"); if (updateID == -1) ret = -1; } check_db(db, ret, &scanner_pid); #ifdef HAVE_INOTIFY if( GETFLAG(INOTIFY_MASK) ) { if (!sqlite3_threadsafe() || sqlite3_libversion_number() < 3005001) DPRINTF(E_ERROR, L_GENERAL, "SQLite library is not threadsafe! " "Inotify will be disabled.\n"); else if (pthread_create(&inotify_thread, NULL, start_inotify, NULL) != 0) DPRINTF(E_FATAL, L_GENERAL, "ERROR: pthread_create() failed for start_inotify. EXITING\n"); } #endif smonitor = OpenAndConfMonitorSocket(); sssdp = OpenAndConfSSDPReceiveSocket(); if (sssdp < 0) { DPRINTF(E_INFO, L_GENERAL, "Failed to open socket for receiving SSDP. Trying to use MiniSSDPd\n"); reload_ifaces(0); /* populate lan_addr[0].str */ if (SubmitServicesToMiniSSDPD(lan_addr[0].str, runtime_vars.port) < 0) DPRINTF(E_FATAL, L_GENERAL, "Failed to connect to MiniSSDPd. EXITING"); } /* open socket for HTTP connections. */ shttpl = OpenAndConfHTTPSocket(runtime_vars.port); if (shttpl < 0) DPRINTF(E_FATAL, L_GENERAL, "Failed to open socket for HTTP. EXITING\n"); DPRINTF(E_WARN, L_GENERAL, "HTTP listening on port %d\n", runtime_vars.port); #ifdef TIVO_SUPPORT if (GETFLAG(TIVO_MASK)) { DPRINTF(E_WARN, L_GENERAL, "TiVo support is enabled.\n"); /* Add TiVo-specific randomize function to sqlite */ ret = sqlite3_create_function(db, "tivorandom", 1, SQLITE_UTF8, NULL, &TiVoRandomSeedFunc, NULL, NULL); if (ret != SQLITE_OK) DPRINTF(E_ERROR, L_TIVO, "ERROR: Failed to add sqlite randomize function for TiVo!\n"); /* open socket for sending Tivo notifications */ sbeacon = OpenAndConfTivoBeaconSocket(); if(sbeacon < 0) DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending Tivo beacon notify " "messages. EXITING\n"); tivo_bcast.sin_family = AF_INET; tivo_bcast.sin_addr.s_addr = htonl(getBcastAddress()); tivo_bcast.sin_port = htons(2190); } #endif reload_ifaces(0); lastnotifytime.tv_sec = time(NULL) + runtime_vars.notify_interval; /* main loop */ while (!quitting) { /* Check if we need to send SSDP NOTIFY messages and do it if * needed */ if (gettimeofday(&timeofday, 0) < 0) { DPRINTF(E_ERROR, L_GENERAL, "gettimeofday(): %s\n", strerror(errno)); timeout.tv_sec = runtime_vars.notify_interval; timeout.tv_usec = 0; } else { /* the comparison is not very precise but who cares ? */ if (timeofday.tv_sec >= (lastnotifytime.tv_sec + runtime_vars.notify_interval)) { DPRINTF(E_DEBUG, L_SSDP, "Sending SSDP notifies\n"); for (i = 0; i < n_lan_addr; i++) { SendSSDPNotifies(lan_addr[i].snotify, lan_addr[i].str, runtime_vars.port, runtime_vars.notify_interval); } memcpy(&lastnotifytime, &timeofday, sizeof(struct timeval)); timeout.tv_sec = runtime_vars.notify_interval; timeout.tv_usec = 0; } else { timeout.tv_sec = lastnotifytime.tv_sec + runtime_vars.notify_interval - timeofday.tv_sec; if (timeofday.tv_usec > lastnotifytime.tv_usec) { timeout.tv_usec = 1000000 + lastnotifytime.tv_usec - timeofday.tv_usec; timeout.tv_sec--; } else timeout.tv_usec = lastnotifytime.tv_usec - timeofday.tv_usec; } #ifdef TIVO_SUPPORT if (sbeacon >= 0) { if (timeofday.tv_sec >= (lastbeacontime.tv_sec + beacon_interval)) { sendBeaconMessage(sbeacon, &tivo_bcast, sizeof(struct sockaddr_in), 1); memcpy(&lastbeacontime, &timeofday, sizeof(struct timeval)); if (timeout.tv_sec > beacon_interval) { timeout.tv_sec = beacon_interval; timeout.tv_usec = 0; } /* Beacons should be sent every 5 seconds or so for the first minute, * then every minute or so thereafter. */ if (beacon_interval == 5 && (timeofday.tv_sec - startup_time) > 60) beacon_interval = 60; } else if (timeout.tv_sec > (lastbeacontime.tv_sec + beacon_interval + 1 - timeofday.tv_sec)) timeout.tv_sec = lastbeacontime.tv_sec + beacon_interval - timeofday.tv_sec; } #endif } if (scanning) { if (!scanner_pid || kill(scanner_pid, 0) != 0) { scanning = 0; updateID++; } } /* select open sockets (SSDP, HTTP listen, and all HTTP soap sockets) */ FD_ZERO(&readset); if (sssdp >= 0) { FD_SET(sssdp, &readset); max_fd = MAX(max_fd, sssdp); } if (shttpl >= 0) { FD_SET(shttpl, &readset); max_fd = MAX(max_fd, shttpl); } #ifdef TIVO_SUPPORT if (sbeacon >= 0) { FD_SET(sbeacon, &readset); max_fd = MAX(max_fd, sbeacon); } #endif if (smonitor >= 0) { FD_SET(smonitor, &readset); max_fd = MAX(max_fd, smonitor); } i = 0; /* active HTTP connections count */ for (e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) { if ((e->socket >= 0) && (e->state <= 2)) { FD_SET(e->socket, &readset); max_fd = MAX(max_fd, e->socket); i++; } } FD_ZERO(&writeset); upnpevents_selectfds(&readset, &writeset, &max_fd); ret = select(max_fd+1, &readset, &writeset, 0, &timeout); if (ret < 0) { if(quitting) goto shutdown; if(errno == EINTR) continue; DPRINTF(E_ERROR, L_GENERAL, "select(all): %s\n", strerror(errno)); DPRINTF(E_FATAL, L_GENERAL, "Failed to select open sockets. EXITING\n"); } upnpevents_processfds(&readset, &writeset); /* process SSDP packets */ if (sssdp >= 0 && FD_ISSET(sssdp, &readset)) { /*DPRINTF(E_DEBUG, L_GENERAL, "Received SSDP Packet\n");*/ ProcessSSDPRequest(sssdp, (unsigned short)runtime_vars.port); } #ifdef TIVO_SUPPORT if (sbeacon >= 0 && FD_ISSET(sbeacon, &readset)) { /*DPRINTF(E_DEBUG, L_GENERAL, "Received UDP Packet\n");*/ ProcessTiVoBeacon(sbeacon); } #endif if (smonitor >= 0 && FD_ISSET(smonitor, &readset)) { ProcessMonitorEvent(smonitor); } /* increment SystemUpdateID if the content database has changed, * and if there is an active HTTP connection, at most once every 2 seconds */ if (i && (timeofday.tv_sec >= (lastupdatetime + 2))) { if (scanning || sqlite3_total_changes(db) != last_changecnt) { updateID++; last_changecnt = sqlite3_total_changes(db); upnp_event_var_change_notify(EContentDirectory); lastupdatetime = timeofday.tv_sec; } } /* process active HTTP connections */ for (e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) { if ((e->socket >= 0) && (e->state <= 2) && (FD_ISSET(e->socket, &readset))) Process_upnphttp(e); } /* process incoming HTTP connections */ if (shttpl >= 0 && FD_ISSET(shttpl, &readset)) { int shttp; socklen_t clientnamelen; struct sockaddr_in clientname; clientnamelen = sizeof(struct sockaddr_in); shttp = accept(shttpl, (struct sockaddr *)&clientname, &clientnamelen); if (shttp<0) { DPRINTF(E_ERROR, L_GENERAL, "accept(http): %s\n", strerror(errno)); } else { struct upnphttp * tmp = 0; DPRINTF(E_DEBUG, L_GENERAL, "HTTP connection from %s:%d\n", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port) ); /*if (fcntl(shttp, F_SETFL, O_NONBLOCK) < 0) { DPRINTF(E_ERROR, L_GENERAL, "fcntl F_SETFL, O_NONBLOCK\n"); }*/ /* Create a new upnphttp object and add it to * the active upnphttp object list */ tmp = New_upnphttp(shttp); if (tmp) { tmp->clientaddr = clientname.sin_addr; LIST_INSERT_HEAD(&upnphttphead, tmp, entries); } else { DPRINTF(E_ERROR, L_GENERAL, "New_upnphttp() failed\n"); close(shttp); } } } /* delete finished HTTP connections */ for (e = upnphttphead.lh_first; e != NULL; e = next) { next = e->entries.le_next; if(e->state >= 100) { LIST_REMOVE(e, entries); Delete_upnphttp(e); } } } shutdown: /* kill the scanner */ if (scanning && scanner_pid) kill(scanner_pid, SIGKILL); /* kill other child processes */ process_reap_children(); free(children); /* close out open sockets */ while (upnphttphead.lh_first != NULL) { e = upnphttphead.lh_first; LIST_REMOVE(e, entries); Delete_upnphttp(e); } if (sssdp >= 0) close(sssdp); if (shttpl >= 0) close(shttpl); #ifdef TIVO_SUPPORT if (sbeacon >= 0) close(sbeacon); #endif if (smonitor >= 0) close(smonitor); for (i = 0; i < n_lan_addr; i++) { SendSSDPGoodbyes(lan_addr[i].snotify); close(lan_addr[i].snotify); } if (inotify_thread) pthread_join(inotify_thread, NULL); sql_exec(db, "UPDATE SETTINGS set VALUE = '%u' where KEY = 'UPDATE_ID'", updateID); sqlite3_close(db); upnpevents_removeSubscribers(); if (pidfilename && unlink(pidfilename) < 0) DPRINTF(E_ERROR, L_GENERAL, "Failed to remove pidfile %s: %s\n", pidfilename, strerror(errno)); log_close(); freeoptions(); exit(EXIT_SUCCESS); } minidlna-1.1.5+dfsg/minidlna.conf000066400000000000000000000064151261774340000167040ustar00rootroot00000000000000# port for HTTP (descriptions, SOAP, media transfer) traffic port=8200 # network interfaces to serve, comma delimited #network_interface=eth0 # specify the user account name or uid to run as #user=jmaggard # set this to the directory you want scanned. # * if you want multiple directories, you can have multiple media_dir= lines # * if you want to restrict a media_dir to specific content types, you # can prepend the types, followed by a comma, to the directory: # + "A" for audio (eg. media_dir=A,/home/jmaggard/Music) # + "V" for video (eg. media_dir=V,/home/jmaggard/Videos) # + "P" for images (eg. media_dir=P,/home/jmaggard/Pictures) # + "PV" for pictures and video (eg. media_dir=PV,/home/jmaggard/digital_camera) media_dir=/opt # set this to merge all media_dir base contents into the root container # note: the default is no #merge_media_dirs=no # set this if you want to customize the name that shows up on your clients #friendly_name=My DLNA Server # set this if you would like to specify the directory where you want MiniDLNA to store its database and album art cache #db_dir=/var/cache/minidlna # set this if you would like to specify the directory where you want MiniDLNA to store its log file #log_dir=/var/log # set this to change the verbosity of the information that is logged # each section can use a different level: off, fatal, error, warn, info, or debug #log_level=general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn # this should be a list of file names to check for when searching for album art # note: names should be delimited with a forward slash ("/") album_art_names=Cover.jpg/cover.jpg/AlbumArtSmall.jpg/albumartsmall.jpg/AlbumArt.jpg/albumart.jpg/Album.jpg/album.jpg/Folder.jpg/folder.jpg/Thumb.jpg/thumb.jpg # set this to no to disable inotify monitoring to automatically discover new files # note: the default is yes inotify=yes # set this to yes to enable support for streaming .jpg and .mp3 files to a TiVo supporting HMO enable_tivo=no # set this to strictly adhere to DLNA standards. # * This will allow server-side downscaling of very large JPEG images, # which may hurt JPEG serving performance on (at least) Sony DLNA products. strict_dlna=no # default presentation url is http address on port 80 #presentation_url=http://www.mylan/index.php # notify interval in seconds. default is 895 seconds. notify_interval=900 # serial and model number the daemon will report to clients # in its XML description serial=12345678 model_number=1 # specify the path to the MiniSSDPd socket #minissdpdsocket=/var/run/minissdpd.sock # use different container as root of the tree # possible values: # + "." - use standard container (this is the default) # + "B" - "Browse Directory" # + "M" - "Music" # + "V" - "Video" # + "P" - "Pictures" # + Or, you can specify the ObjectID of your desired root container (eg. 1$F for Music/Playlists) # if you specify "B" and client device is audio-only then "Music/Folders" will be used as root #root_container=. # always force SortCriteria to this value, regardless of the SortCriteria passed by the client #force_sort_criteria=+upnp:class,+upnp:originalTrackNumber,+dc:title # maximum number of simultaneous connections # note: many clients open several simultaneous connections while streaming #max_connections=50 minidlna-1.1.5+dfsg/minidlna.conf.5000066400000000000000000000127241261774340000170470ustar00rootroot00000000000000.\" minidlna.conf man page .TH minidlna.conf 5 "October 2012" .SH NAME minidlna .SH DESCRIPTION .PP .B minidlna is a light weight but very functional DLNA server. In most cases, the defaults do not need modifications. The global configuration file is /etc/minidlna.conf but local users without system root access can run minidlna with their own configuration file. .SH OPTIONS .PP The following are user configurable options in /etc/minidlna.conf. minidlna runs by default as user nobody, so make sure system permissions are set correctly for read access to media and write access to cache and log dirs. .IP "\fBfriendly_name\fP" The name you want your media server seen as, EG: friendly_name=Home Media Server .IP "\fBport\fP" .nf Port for HTTP (descriptions, SOAP, media transfer) traffic etc, defaults to 8200. There should be no need to change this. .fi .IP "\fBnetwork_interface\fP" Network interfaces to serve, comma delimited. Defaults to all. .IP "\fBstrict_dlna\fP" .nf Set this to strictly adhere to DLNA standards. This will allow server-side downscaling of very large JPEG images, which may hurt JPEG serving performance on (at least) Sony DLNA products. .fi .IP "\fBnotify_interval\fP" Notify interval in seconds. The default is 895 seconds. .IP "\fBminissdpdsocket\fP" .nf Specify the path to the MiniSSDPd socket, EG: minissdpdsocket=/var/run/minissdpd.sock .fi .IP "\fBserial\fP" .nf Serial number the daemon will report to clients in its XML description. Defaults to 12345678 .fi .IP "\fBmodel\fP" .nf Model number the daemon will report to clients in its XML description. Defaults to 1 .fi .IP "\fBmedia_dir\fP" .nf Path to the directory containing the media files minidlna should share. Use this option multile times if you have more than one directory to share. Example: media_dir=/opt/multimedia/videos media_dir=/opt/multimedia/movies .fi .PP You can also restrict an entry to a specific media type, you do this by using the following syntax: .nf the letter 'A', 'V' or 'P', followed by a comma (',') followed by the path. The meaning of the first letter is as follows: 'A' for audio files 'V' for video files 'P' for image files For example, if you want to include only video files located in /opt/multimedia/videos directory, and only music in /opt/multimedia/music, then you would use media_dir=V,/opt/multimedia/videos media_dir=A,/opt/multimedia/music Another example would be media_dir=V,/opt/multimedia/videos media_dir=V,/opt/multimedia/movies media_dir=A,/opt/multimedia/music Or, if you did not care what type it finds, then you could use media_dir=/opt/multimedia/videos media_dir=/opt/multimedia/movies media_dir=/opt/multimedia/music You can mix it up, find anything in music, but only Videos, in videos and movies media_dir=V,/opt/multimedia/videos media_dir=V,/opt/multimedia/movies media_dir=/opt/multimedia/music .fi .IP "\fBpresentation_url\fP" .nf Default presentation url is http address on port 80 EG: presentation_url=http://www.mediaserver.lan/index.php .fi .IP "\fBdb_dir\fP" Where minidlna stores the data files, including Album caceh files, by default this is /var/cache/minidlna .IP "\fBlog_dir\fP" Path to the directory where the log file upnp-av.log should be stored, this defaults to /var/log .IP "\fBlog_level\fP" Set this to change the verbosity of the information that is logged each section can use a different level: off, fatal, error, warn, info, or debug .nf Example log_level=general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn .fi .IP "\fBinotify\fP" Set to 'yes' to enable inotify monitoring of the files under media_dir to automatically discover new files. Set to 'no' to disable inotify. .IP "\fBalbum_art_names\fP" This should be a list of file names to check for when searching for album art and names should be delimited with a forward slash ("/"). .nf Example album_art_names=Cover.jpg/cover.jpg/AlbumArtSmall.jpg/albumartsmall.jpg/AlbumArt.jpg /albumart.jpg/Album.jpg/album.jpg/Folder.jpg/folder.jpg/Thumb.jpg/thumb.jpg .fi .IP "\fBenable_tivo\fP" Set this to yes to enable support for streaming .jpg and .mp3 files to a TiVo supporting HMO, default is no. .IP "\fBroot_container\fP" Use a different container as the root of the tree exposed to clients. .nf The possible values are: '.' to use the standard container (this is the default) 'B' to use the "Browse Directory" container; 'M' to use the "Music" container; 'V' to use the "Video" container; 'P' to use the "Pictures" container. If you specify 'B' and the client device is audio only, then "Music/Folders" will be used as root container and you wont see Videos. .fi .IP "\fBforce_sort_criteria\fP" Always force SortCriteria to this value, regardless of the SortCriteria passed by the client. .nf Example force_sort_criteria=+upnp:class,+upnp:originalTrackNumber,+dc:title .fi .SH VERSION This manpage corresponds to minidlna version 1.0.25 .SH AUTHOR .nf minidlna developed by Justin Maggard https://sourceforge.net/projects/minidlna/ man page written by Noel Butler .fi .SH LICENSE GPL .SH FILES /etc/minidlna.conf .SH SEE ALSO minidlna(8) minidlna-1.1.5+dfsg/minidlnad.8000066400000000000000000000040171261774340000162660ustar00rootroot00000000000000.\" minidlnad man page .TH minidlnad 8 "October 2012" .SH NAME minidlnad \- MiniDLNA DLNA/UPnP-AV Server .SH SYNOPSIS minidlnad options .SH DESCRIPTION .PP .B minidlna is a lightweight but very functional DLNA/UPnP-AV server allowing easy sharing of your media files including pictures to any capable device or client on your local wired and wireless network. Common PC software like totem is a good lightweight and fast choice for Linux/BSD; more fully featured software for Linux, Windows and MAC is XBMC, also Mediahouse on Android, and any media sharing capable DVR/TV etc. .SH OPTIONS .PP .B minidlna's options are normally set from the global /etc/minidlna.conf file. .nf But there may be times when you need to temporarily alter some of these options, you can do this by running minidlna with the following command line switches. .fi .IP "\fB\-R\fR \fIRescan\fR" This forces minidlna to rescan all of the media_dir directories. .IP "\fB\-f\fR \fIconfig_file\fR" Run minidlna with a different configuration file than the global default. .IP "\fB\-p\fR \fIport\fR" Allows to run minidlna on a different port. .IP "\fB\-w\fR \fIpresentation_url\fR" Change the default presentation url address. .IP "\fB\-d\fR \fIdebug\fR" minidlna will run in the foreground with debug output. .IP "\fB\-P\fR \fIpid_filename\fR" Specify a location for the PID file. .IP "\fB\-m\fR \fImodel\fR" Force minidlna to report a specific model number. .IP "\fB\-s\fR \fIserial\fR" Force minidlna to report a specific serial number. .IP "\fB\-t\fR \fInotify_interval\fR" Change the notify interval, in seconds. Defaults to 895 seconds. .IP "\fB\-h\fR \fIhelp\fR" Show basic switch options for running minidlna. .IP "\fB\-V\fR \fIVersion\fR" Shows the program version number and exits. .SH VERSION This man page corresponds to minidlna version 1.1.0 .SH AUTHOR .nf minidlna developed by Justin Maggard https://sourceforge.net/projects/minidlna/ man page written by Noel Butler .fi .SH FILES /usr/sbin/minidlnad .SH SEE ALSO minidlna.conf(5) minidlna-1.1.5+dfsg/minidlnapath.h000066400000000000000000000045031261774340000170570ustar00rootroot00000000000000/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * * Copyright (c) 2006-2008, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __MINIDLNAPATH_H__ #define __MINIDLNAPATH_H__ #include "config.h" /* Paths and other URLs in the minidlna http server */ #define ROOTDESC_PATH "/rootDesc.xml" #define CONTENTDIRECTORY_PATH "/ContentDir.xml" #define CONTENTDIRECTORY_CONTROLURL "/ctl/ContentDir" #define CONTENTDIRECTORY_EVENTURL "/evt/ContentDir" #define CONNECTIONMGR_PATH "/ConnectionMgr.xml" #define CONNECTIONMGR_CONTROLURL "/ctl/ConnectionMgr" #define CONNECTIONMGR_EVENTURL "/evt/ConnectionMgr" #define X_MS_MEDIARECEIVERREGISTRAR_PATH "/X_MS_MediaReceiverRegistrar.xml" #define X_MS_MEDIARECEIVERREGISTRAR_CONTROLURL "/ctl/X_MS_MediaReceiverRegistrar" #define X_MS_MEDIARECEIVERREGISTRAR_EVENTURL "/evt/X_MS_MediaReceiverRegistrar" #endif minidlna-1.1.5+dfsg/minidlnatypes.h000066400000000000000000000057231261774340000172740ustar00rootroot00000000000000/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * * Copyright (c) 2006-2007, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __MINIDLNATYPES_H__ #define __MINIDLNATYPES_H__ #include "config.h" #include "clients.h" #include #include #define MAX_LAN_ADDR 4 /* structure for storing lan addresses * with ascii representation and mask */ struct lan_addr_s { char str[16]; /* example: 192.168.0.1 */ struct in_addr addr; /* ip */ struct in_addr mask; /* netmask */ int snotify; /* notify socket */ unsigned int ifindex; /* interface index */ }; struct runtime_vars_s { int port; /* HTTP Port */ int notify_interval; /* seconds between SSDP announces */ int max_connections; /* max number of simultaneous conenctions */ const char *root_container; /* root ObjectID (instead of "0") */ const char *ifaces[MAX_LAN_ADDR]; /* list of configured network interfaces */ }; struct string_s { char *data; // ptr to start of memory area size_t off; size_t size; }; typedef uint8_t media_types; #define NO_MEDIA 0x00 #define TYPE_AUDIO 0x01 #define TYPE_VIDEO 0x02 #define TYPE_IMAGES 0x04 #define ALL_MEDIA TYPE_AUDIO|TYPE_VIDEO|TYPE_IMAGES enum file_types { TYPE_UNKNOWN, TYPE_DIR, TYPE_FILE }; struct media_dir_s { char *path; /* base path */ media_types types; /* types of files to scan */ struct media_dir_s *next; }; struct album_art_name_s { char *name; /* base path */ uint8_t wildcard; struct album_art_name_s *next; }; #endif minidlna-1.1.5+dfsg/minissdp.c000066400000000000000000000553131261774340000162350ustar00rootroot00000000000000/* MiniDLNA media server * This file is part of MiniDLNA. * * The code herein is based on the MiniUPnP Project. * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * * Copyright (c) 2006, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "minidlnapath.h" #include "upnphttp.h" #include "upnpglobalvars.h" #include "upnpreplyparse.h" #include "getifaddr.h" #include "minissdp.h" #include "codelength.h" #include "utils.h" #include "log.h" /* SSDP ip/port */ #define SSDP_PORT (1900) #define SSDP_MCAST_ADDR ("239.255.255.250") static int AddMulticastMembership(int s, struct lan_addr_s *iface) { int ret; #ifdef HAVE_STRUCT_IP_MREQN struct ip_mreqn imr; /* Ip multicast membership */ /* setting up imr structure */ memset(&imr, '\0', sizeof(imr)); imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR); imr.imr_ifindex = iface->ifindex; #else struct ip_mreq imr; /* Ip multicast membership */ /* setting up imr structure */ memset(&imr, '\0', sizeof(imr)); imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR); imr.imr_interface.s_addr = iface->addr.s_addr; #endif /* Setting the socket options will guarantee, tha we will only receive * multicast traffic on a specific Interface. * In addition the kernel is instructed to send an igmp message (choose * mcast group) on the specific interface/subnet. */ ret = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&imr, sizeof(imr)); if (ret < 0 && errno != EADDRINUSE) { DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp, IP_ADD_MEMBERSHIP): %s\n", strerror(errno)); return -1; } return 0; } /* Open and configure the socket listening for * SSDP udp packets sent on 239.255.255.250 port 1900 */ int OpenAndConfSSDPReceiveSocket(void) { int s; int i = 1; struct sockaddr_in sockname; s = socket(PF_INET, SOCK_DGRAM, 0); if (s < 0) { DPRINTF(E_ERROR, L_SSDP, "socket(udp): %s\n", strerror(errno)); return -1; } if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) DPRINTF(E_WARN, L_SSDP, "setsockopt(udp, SO_REUSEADDR): %s\n", strerror(errno)); #ifdef __linux__ if (setsockopt(s, IPPROTO_IP, IP_PKTINFO, &i, sizeof(i)) < 0) DPRINTF(E_WARN, L_SSDP, "setsockopt(udp, IP_PKTINFO): %s\n", strerror(errno)); #endif memset(&sockname, 0, sizeof(struct sockaddr_in)); sockname.sin_family = AF_INET; sockname.sin_port = htons(SSDP_PORT); /* NOTE: Binding a socket to a UDP multicast address means, that we just want * to receive datagramms send to this multicast address. * To specify the local nics we want to use we have to use setsockopt, * see AddMulticastMembership(...). */ sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR); if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) { DPRINTF(E_ERROR, L_SSDP, "bind(udp): %s\n", strerror(errno)); close(s); return -1; } return s; } /* open the UDP socket used to send SSDP notifications to * the multicast group reserved for them */ int OpenAndConfSSDPNotifySocket(struct lan_addr_s *iface) { int s; unsigned char loopchar = 0; int bcast = 1; uint8_t ttl = 4; struct in_addr mc_if; struct sockaddr_in sockname; s = socket(PF_INET, SOCK_DGRAM, 0); if (s < 0) { DPRINTF(E_ERROR, L_SSDP, "socket(udp_notify): %s\n", strerror(errno)); return -1; } mc_if.s_addr = iface->addr.s_addr; if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopchar, sizeof(loopchar)) < 0) { DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, IP_MULTICAST_LOOP): %s\n", strerror(errno)); close(s); return -1; } if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&mc_if, sizeof(mc_if)) < 0) { DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, IP_MULTICAST_IF): %s\n", strerror(errno)); close(s); return -1; } setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) < 0) { DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, SO_BROADCAST): %s\n", strerror(errno)); close(s); return -1; } memset(&sockname, 0, sizeof(struct sockaddr_in)); sockname.sin_family = AF_INET; sockname.sin_addr.s_addr = iface->addr.s_addr; if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0) { DPRINTF(E_ERROR, L_SSDP, "bind(udp_notify): %s\n", strerror(errno)); close(s); return -1; } if (AddMulticastMembership(sssdp, iface) < 0) { DPRINTF(E_WARN, L_SSDP, "Failed to add multicast membership for address %s\n", iface->str); } return s; } static const char * const known_service_types[] = { uuidvalue, "upnp:rootdevice", "urn:schemas-upnp-org:device:MediaServer:", "urn:schemas-upnp-org:service:ContentDirectory:", "urn:schemas-upnp-org:service:ConnectionManager:", "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:", 0 }; static void _usleep(long usecs) { struct timespec sleep_time; sleep_time.tv_sec = 0; sleep_time.tv_nsec = usecs * 1000; nanosleep(&sleep_time, NULL); } /* not really an SSDP "announce" as it is the response * to a SSDP "M-SEARCH" */ static void SendSSDPResponse(int s, struct sockaddr_in sockname, int st_no, const char *host, unsigned short port) { int l, n; char buf[512]; char tmstr[30]; time_t tm = time(NULL); /* * follow guideline from document "UPnP Device Architecture 1.0" * uppercase is recommended. * DATE: is recommended * SERVER: OS/ver UPnP/1.0 minidlna/1.0 * - check what to put in the 'Cache-Control' header * */ strftime(tmstr, sizeof(tmstr), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&tm)); l = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n" "CACHE-CONTROL: max-age=%u\r\n" "DATE: %s\r\n" "ST: %s%s\r\n" "USN: %s%s%s%s\r\n" "EXT:\r\n" "SERVER: " MINIDLNA_SERVER_STRING "\r\n" "LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n" "Content-Length: 0\r\n" "\r\n", (runtime_vars.notify_interval<<1)+10, tmstr, known_service_types[st_no], (st_no > 1 ? "1" : ""), uuidvalue, (st_no > 0 ? "::" : ""), (st_no > 0 ? known_service_types[st_no] : ""), (st_no > 1 ? "1" : ""), host, (unsigned int)port); DPRINTF(E_DEBUG, L_SSDP, "Sending M-SEARCH response to %s:%d ST: %s\n", inet_ntoa(sockname.sin_addr), ntohs(sockname.sin_port), known_service_types[st_no]); n = sendto(s, buf, l, 0, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) ); if (n < 0) DPRINTF(E_ERROR, L_SSDP, "sendto(udp): %s\n", strerror(errno)); } void SendSSDPNotifies(int s, const char *host, unsigned short port, unsigned int interval) { struct sockaddr_in sockname; int l, n, dup, i=0; unsigned int lifetime; char bufr[512]; memset(&sockname, 0, sizeof(struct sockaddr_in)); sockname.sin_family = AF_INET; sockname.sin_port = htons(SSDP_PORT); sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR); lifetime = (interval << 1) + 10; for (dup = 0; dup < 2; dup++) { if (dup) _usleep(200000); i = 0; while (known_service_types[i]) { l = snprintf(bufr, sizeof(bufr), "NOTIFY * HTTP/1.1\r\n" "HOST:%s:%d\r\n" "CACHE-CONTROL:max-age=%u\r\n" "LOCATION:http://%s:%d" ROOTDESC_PATH"\r\n" "SERVER: " MINIDLNA_SERVER_STRING "\r\n" "NT:%s%s\r\n" "USN:%s%s%s%s\r\n" "NTS:ssdp:alive\r\n" "\r\n", SSDP_MCAST_ADDR, SSDP_PORT, lifetime, host, port, known_service_types[i], (i > 1 ? "1" : ""), uuidvalue, (i > 0 ? "::" : ""), (i > 0 ? known_service_types[i] : ""), (i > 1 ? "1" : "")); if (l >= sizeof(bufr)) { DPRINTF(E_WARN, L_SSDP, "SendSSDPNotifies(): truncated output\n"); l = sizeof(bufr); } DPRINTF(E_MAXDEBUG, L_SSDP, "Sending ssdp:alive [%d]\n", s); n = sendto(s, bufr, l, 0, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)); if (n < 0) DPRINTF(E_ERROR, L_SSDP, "sendto(udp_notify=%d, %s): %s\n", s, host, strerror(errno)); i++; } } } static void ParseUPnPClient(char *location) { char buf[8192]; struct sockaddr_in dest; int s, n, do_headers = 0, nread = 0; struct timeval tv; char *addr, *path, *port_str; long port = 80; char *off = NULL, *p; int content_len = sizeof(buf); struct NameValueParserData xml; struct client_cache_s *client; int type = 0; char *model, *serial, *name; if (strncmp(location, "http://", 7) != 0) return; path = location + 7; port_str = strsep(&path, "/"); if (!path) return; addr = strsep(&port_str, ":"); if (port_str) { port = strtol(port_str, NULL, 10); if (!port) port = 80; } memset(&dest, '\0', sizeof(dest)); if (!inet_aton(addr, &dest.sin_addr)) return; /* Check if the client is already in cache */ dest.sin_family = AF_INET; dest.sin_port = htons(port); s = socket(PF_INET, SOCK_STREAM, 0); if (s < 0) return; tv.tv_sec = 0; tv.tv_usec = 500000; setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); if (connect(s, (struct sockaddr*)&dest, sizeof(struct sockaddr_in)) < 0) goto close; n = snprintf(buf, sizeof(buf), "GET /%s HTTP/1.0\r\n" "HOST: %s:%ld\r\n\r\n", path, addr, port); if (write(s, buf, n) < 1) goto close; while ((n = read(s, buf+nread, sizeof(buf)-nread-1)) > 0) { nread += n; buf[nread] = '\0'; n = nread - 4; p = buf; while (!off && (n-- > 0)) { if (p[0] == '\r' && p[1] == '\n' && p[2] == '\r' && p[3] == '\n') { off = p + 4; do_headers = 1; } p++; } if (!off) continue; if (do_headers) { p = buf; if (strncmp(p, "HTTP/", 5) != 0) goto close; while (*p != ' ' && *p != '\t') p++; /* If we don't get a 200 status, ignore it */ if (strtol(p, NULL, 10) != 200) goto close; p = strcasestr(p, "Content-Length:"); if (p) content_len = strtol(p+15, NULL, 10); do_headers = 0; } if ((buf + nread - off) >= content_len) break; } close: close(s); if (!off) return; nread -= off - buf; ParseNameValue(off, nread, &xml, 0); model = GetValueFromNameValueList(&xml, "modelName"); serial = GetValueFromNameValueList(&xml, "serialNumber"); name = GetValueFromNameValueList(&xml, "friendlyName"); if (model) { int i; DPRINTF(E_DEBUG, L_SSDP, "Model: %s\n", model); for (i = 0; client_types[i].name; i++) { if (client_types[i].match_type != EModelName) continue; if (strstr(model, client_types[i].match) != NULL) { type = i; break; } } /* Special Samsung handling. It's very hard to tell Series A from B */ if (type > 0 && client_types[type].type == ESamsungSeriesB) { if (serial) { DPRINTF(E_DEBUG, L_SSDP, "Serial: %s\n", serial); /* The Series B I saw was 20081224DMR. Series A should be older than that. */ if (atoi(serial) < 20081201) type = 0; } else { type = 0; } } if (type == 0 && name != NULL) { for (i = 0; client_types[i].name; i++) { if (client_types[i].match_type != EFriendlyNameSSDP) continue; if (strcmp(name, client_types[i].match) == 0) { type = i; break; } } } } ClearNameValueList(&xml); if (!type) return; /* Add this client to the cache if it's not there already. */ client = SearchClientCache(dest.sin_addr, 1); if (!client) { AddClientCache(dest.sin_addr, type); } else { client->type = &client_types[type]; client->age = time(NULL); } } /* ProcessSSDPRequest() * process SSDP M-SEARCH requests and responds to them */ void ProcessSSDPRequest(int s, unsigned short port) { int n; char bufr[1500]; struct sockaddr_in sendername; int i; char *st = NULL, *mx = NULL, *man = NULL, *mx_end = NULL; int man_len = 0; #ifdef __linux__ char cmbuf[CMSG_SPACE(sizeof(struct in_pktinfo))]; struct iovec iovec = { .iov_base = bufr, .iov_len = sizeof(bufr)-1 }; struct msghdr mh = { .msg_name = &sendername, .msg_namelen = sizeof(struct sockaddr_in), .msg_iov = &iovec, .msg_iovlen = 1, .msg_control = cmbuf, .msg_controllen = sizeof(cmbuf) }; n = recvmsg(s, &mh, 0); #else socklen_t len_r = sizeof(struct sockaddr_in); n = recvfrom(s, bufr, sizeof(bufr)-1, 0, (struct sockaddr *)&sendername, &len_r); #endif if (n < 0) { DPRINTF(E_ERROR, L_SSDP, "recvfrom(udp): %s\n", strerror(errno)); return; } bufr[n] = '\0'; n -= 2; if (memcmp(bufr, "NOTIFY", 6) == 0) { char *loc = NULL, *srv = NULL, *nts = NULL, *nt = NULL; int loc_len = 0; //DEBUG DPRINTF(E_DEBUG, L_SSDP, "Received SSDP notify:\n%.*s", n, bufr); for (i = 0; i < n; i++) { if( bufr[i] == '*' ) break; } if (strcasestrc(bufr+i, "HTTP/1.1", '\r') == NULL) return; while (i < n) { while ((i < n) && (bufr[i] != '\r' || bufr[i+1] != '\n')) i++; i += 2; if (strncasecmp(bufr+i, "SERVER:", 7) == 0) { srv = bufr+i+7; while (*srv == ' ' || *srv == '\t') srv++; } else if (strncasecmp(bufr+i, "LOCATION:", 9) == 0) { loc = bufr+i+9; while (*loc == ' ' || *loc == '\t') loc++; while (loc[loc_len]!='\r' && loc[loc_len]!='\n') loc_len++; } else if (strncasecmp(bufr+i, "NTS:", 4) == 0) { nts = bufr+i+4; while (*nts == ' ' || *nts == '\t') nts++; } else if (strncasecmp(bufr+i, "NT:", 3) == 0) { nt = bufr+i+3; while(*nt == ' ' || *nt == '\t') nt++; } } if (!loc || !srv || !nt || !nts || (strncmp(nts, "ssdp:alive", 10) != 0) || (strncmp(nt, "urn:schemas-upnp-org:device:MediaRenderer", 41) != 0)) return; loc[loc_len] = '\0'; if ((strncmp(srv, "Allegro-Software-RomPlug", 24) == 0) || /* Roku */ (strstr(loc, "SamsungMRDesc.xml") != NULL) || /* Samsung TV */ (strstrc(srv, "DigiOn DiXiM", '\r') != NULL)) /* Marantz Receiver */ { /* Check if the client is already in cache */ struct client_cache_s *client = SearchClientCache(sendername.sin_addr, 1); if (client) { if (client->type->type < EStandardDLNA150 && client->type->type != ESamsungSeriesA) { client->age = time(NULL); return; } } ParseUPnPClient(loc); } } else if (memcmp(bufr, "M-SEARCH", 8) == 0) { int st_len = 0, mx_len = 0, mx_val = 0; //DPRINTF(E_DEBUG, L_SSDP, "Received SSDP request:\n%.*s\n", n, bufr); for (i = 0; i < n; i++) { if (bufr[i] == '*') break; } if (strcasestrc(bufr+i, "HTTP/1.1", '\r') == NULL) return; while (i < n) { while ((i < n) && (bufr[i] != '\r' || bufr[i+1] != '\n')) i++; i += 2; if (strncasecmp(bufr+i, "ST:", 3) == 0) { st = bufr+i+3; st_len = 0; while (*st == ' ' || *st == '\t') st++; while (st[st_len]!='\r' && st[st_len]!='\n') st_len++; } else if (strncasecmp(bufr+i, "MX:", 3) == 0) { mx = bufr+i+3; mx_len = 0; while (*mx == ' ' || *mx == '\t') mx++; while (mx[mx_len]!='\r' && mx[mx_len]!='\n') mx_len++; mx_val = strtol(mx, &mx_end, 10); } else if (strncasecmp(bufr+i, "MAN:", 4) == 0) { man = bufr+i+4; man_len = 0; while (*man == ' ' || *man == '\t') man++; while (man[man_len]!='\r' && man[man_len]!='\n') man_len++; } } /*DPRINTF(E_INFO, L_SSDP, "SSDP M-SEARCH packet received from %s:%d\n", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port) );*/ if (GETFLAG(DLNA_STRICT_MASK) && (ntohs(sendername.sin_port) <= 1024 || ntohs(sendername.sin_port) == 1900)) { DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad source port %d]\n", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port)); } else if (!man || (strncmp(man, "\"ssdp:discover\"", 15) != 0)) { DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad %s header '%.*s']\n", inet_ntoa(sendername.sin_addr), "MAN", man_len, man); } else if (!mx || mx == mx_end || mx_val < 0) { DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad %s header '%.*s']\n", inet_ntoa(sendername.sin_addr), "MX", mx_len, mx); } else if (st && (st_len > 0)) { int l; #ifdef __linux__ char host[40] = "127.0.0.1"; struct cmsghdr *cmsg; /* find the interface we received the msg from */ for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) { struct in_addr addr; struct in_pktinfo *pi; /* ignore the control headers that don't match what we want */ if (cmsg->cmsg_level != IPPROTO_IP || cmsg->cmsg_type != IP_PKTINFO) continue; pi = (struct in_pktinfo *)CMSG_DATA(cmsg); addr = pi->ipi_spec_dst; inet_ntop(AF_INET, &addr, host, sizeof(host)); } #else const char *host; int iface = 0; /* find in which sub network the client is */ for (i = 0; i < n_lan_addr; i++) { if((sendername.sin_addr.s_addr & lan_addr[i].mask.s_addr) == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr)) { iface = i; break; } } if (n_lan_addr == i) { DPRINTF(E_DEBUG, L_SSDP, "Ignoring SSDP M-SEARCH on other interface [%s]\n", inet_ntoa(sendername.sin_addr)); return; } host = lan_addr[iface].str; #endif DPRINTF(E_DEBUG, L_SSDP, "SSDP M-SEARCH from %s:%d ST: %.*s, MX: %.*s, MAN: %.*s\n", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port), st_len, st, mx_len, mx, man_len, man); /* Responds to request with a device as ST header */ for (i = 0; known_service_types[i]; i++) { l = strlen(known_service_types[i]); if ((l > st_len) || (memcmp(st, known_service_types[i], l) != 0)) continue; if (st_len != l) { /* Check version number - we only support 1. */ if ((st[l-1] == ':') && (st[l] == '1')) l++; while (l < st_len) { if (isdigit(st[l])) break; if (isspace(st[l])) { l++; continue; } DPRINTF(E_MAXDEBUG, L_SSDP, "Ignoring SSDP M-SEARCH with bad extra data '%c' [%s]\n", st[l], inet_ntoa(sendername.sin_addr)); break; } if (l != st_len) break; } _usleep(random()>>20); SendSSDPResponse(s, sendername, i, host, port); return; } /* Responds to request with ST: ssdp:all */ /* strlen("ssdp:all") == 8 */ if ((st_len == 8) && (memcmp(st, "ssdp:all", 8) == 0)) { for (i=0; known_service_types[i]; i++) { l = strlen(known_service_types[i]); SendSSDPResponse(s, sendername, i, host, port); } } } else { DPRINTF(E_INFO, L_SSDP, "Invalid SSDP M-SEARCH from %s:%d\n", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port)); } } else if (memcmp(bufr, "YOUKU-NOTIFY", 12) == 0) { return; } else { DPRINTF(E_WARN, L_SSDP, "Unknown udp packet received from %s:%d\n", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port)); } } /* This will broadcast ssdp:byebye notifications to inform * the network that UPnP is going down. */ int SendSSDPGoodbyes(int s) { struct sockaddr_in sockname; int n, l; int i; int dup, ret = 0; char bufr[512]; memset(&sockname, 0, sizeof(struct sockaddr_in)); sockname.sin_family = AF_INET; sockname.sin_port = htons(SSDP_PORT); sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR); for (dup = 0; dup < 2; dup++) { for (i = 0; known_service_types[i]; i++) { l = snprintf(bufr, sizeof(bufr), "NOTIFY * HTTP/1.1\r\n" "HOST:%s:%d\r\n" "NT:%s%s\r\n" "USN:%s%s%s%s\r\n" "NTS:ssdp:byebye\r\n" "\r\n", SSDP_MCAST_ADDR, SSDP_PORT, known_service_types[i], (i > 1 ? "1" : ""), uuidvalue, (i > 0 ? "::" : ""), (i > 0 ? known_service_types[i] : ""), (i > 1 ? "1" : "")); DPRINTF(E_MAXDEBUG, L_SSDP, "Sending ssdp:byebye [%d]\n", s); n = sendto(s, bufr, l, 0, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) ); if (n < 0) { DPRINTF(E_ERROR, L_SSDP, "sendto(udp_shutdown=%d): %s\n", s, strerror(errno)); ret = -1; break; } } } return ret; } /* SubmitServicesToMiniSSDPD() : * register services offered by MiniUPnPd to a running instance of * MiniSSDPd */ int SubmitServicesToMiniSSDPD(const char *host, unsigned short port) { struct sockaddr_un addr; int s; unsigned char buffer[2048]; char strbuf[256]; unsigned char *p; int i, l; s = socket(AF_UNIX, SOCK_STREAM, 0); if (s < 0) { DPRINTF(E_ERROR, L_SSDP, "socket(unix): %s", strerror(errno)); return -1; } addr.sun_family = AF_UNIX; strncpyt(addr.sun_path, minissdpdsocketpath, sizeof(addr.sun_path)); if (connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) { DPRINTF(E_ERROR, L_SSDP, "connect(\"%s\"): %s", minissdpdsocketpath, strerror(errno)); close(s); return -1; } for (i = 0; known_service_types[i]; i++) { buffer[0] = 4; p = buffer + 1; l = strlen(known_service_types[i]); if (i > 0) l++; CODELENGTH(l, p); memcpy(p, known_service_types[i], l); if (i > 0) p[l-1] = '1'; p += l; l = snprintf(strbuf, sizeof(strbuf), "%s::%s%s", uuidvalue, known_service_types[i], (i==0)?"":"1"); CODELENGTH(l, p); memcpy(p, strbuf, l); p += l; l = strlen(MINIDLNA_SERVER_STRING); CODELENGTH(l, p); memcpy(p, MINIDLNA_SERVER_STRING, l); p += l; l = snprintf(strbuf, sizeof(strbuf), "http://%s:%u" ROOTDESC_PATH, host, (unsigned int)port); CODELENGTH(l, p); memcpy(p, strbuf, l); p += l; if(write(s, buffer, p - buffer) < 0) { DPRINTF(E_ERROR, L_SSDP, "write(): %s", strerror(errno)); close(s); return -1; } } close(s); return 0; } minidlna-1.1.5+dfsg/minissdp.h000066400000000000000000000037611261774340000162420ustar00rootroot00000000000000/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * * Copyright (c) 2006-2007, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __MINISSDP_H__ #define __MINISSDP_H__ int OpenAndConfSSDPReceiveSocket(void); int OpenAndConfSSDPNotifySocket(struct lan_addr_s *iface); void SendSSDPNotifies(int s, const char *host, unsigned short port, unsigned int lifetime); void ProcessSSDPRequest(int s, unsigned short port); int SendSSDPGoodbyes(int s); int SubmitServicesToMiniSSDPD(const char *host, unsigned short port); #endif minidlna-1.1.5+dfsg/minixml.c000066400000000000000000000113731261774340000160620ustar00rootroot00000000000000/* minixml.c : the minimum size a xml parser can be ! */ /* Project : miniupnp * webpage: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * Author : Thomas Bernard Copyright (c) 2005-2007, Thomas BERNARD All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "minixml.h" #include "upnpreplyparse.h" /* parseatt : used to parse the argument list * return 0 (false) in case of success and -1 (true) if the end * of the xmlbuffer is reached. */ int parseatt(struct xmlparser * p) { const char * attname; int attnamelen; const char * attvalue; int attvaluelen; while(p->xml < p->xmlend) { if(*p->xml=='/' || *p->xml=='>') return 0; if( !IS_WHITE_SPACE(*p->xml) ) { char sep; attname = p->xml; attnamelen = 0; while(*p->xml!='=' && !IS_WHITE_SPACE(*p->xml) ) { attnamelen++; p->xml++; if(p->xml >= p->xmlend) return -1; } while(*(p->xml++) != '=') { if(p->xml >= p->xmlend) return -1; } while(IS_WHITE_SPACE(*p->xml)) { p->xml++; if(p->xml >= p->xmlend) return -1; } sep = *p->xml; if(sep=='\'' || sep=='\"') { p->xml++; if(p->xml >= p->xmlend) return -1; attvalue = p->xml; attvaluelen = 0; while(*p->xml != sep) { attvaluelen++; p->xml++; if(p->xml >= p->xmlend) return -1; } } else { attvalue = p->xml; attvaluelen = 0; while( !IS_WHITE_SPACE(*p->xml) && *p->xml != '>' && *p->xml != '/') { attvaluelen++; p->xml++; if(p->xml >= p->xmlend) return -1; } } /*printf("%.*s='%.*s'\n", attnamelen, attname, attvaluelen, attvalue);*/ if(p->attfunc) p->attfunc(p->data, attname, attnamelen, attvalue, attvaluelen); } p->xml++; } return -1; } /* parseelt parse the xml stream and * call the callback functions when needed... */ void parseelt(struct xmlparser * p) { int i; const char * elementname; while(p->xml < (p->xmlend - 1)) { if((p->xml)[0]=='<' && (p->xml)[1]!='?') { i = 0; elementname = ++p->xml; while( !IS_WHITE_SPACE(*p->xml) && (*p->xml!='>') && (*p->xml!='/') ) { i++; p->xml++; if (p->xml >= p->xmlend) return; /* to ignore namespace : */ if(*p->xml==':') { i = 0; elementname = ++p->xml; } } if(i>0) { if(p->starteltfunc) p->starteltfunc(p->data, elementname, i); if(parseatt(p)) return; if(*p->xml!='/') { const char * data; i = 0; data = ++p->xml; if (p->xml >= p->xmlend) return; while( IS_WHITE_SPACE(*p->xml) ) { p->xml++; if (p->xml >= p->xmlend) return; } while(*p->xml!='<') { i++; p->xml++; if (p->xml >= p->xmlend) return; } if (p->datafunc) { if (i > 0 || (p->flags & XML_STORE_EMPTY_FL)) p->datafunc(p->data, data, i); } } } else if(*p->xml == '/') { i = 0; elementname = ++p->xml; if (p->xml >= p->xmlend) return; while((*p->xml != '>')) { i++; p->xml++; if (p->xml >= p->xmlend) return; } if(p->endeltfunc) p->endeltfunc(p->data, elementname, i); p->xml++; } } else { p->xml++; } } } /* the parser must be initialized before calling this function */ void parsexml(struct xmlparser * parser) { parser->xml = parser->xmlstart; parser->xmlend = parser->xmlstart + parser->xmlsize; parseelt(parser); } minidlna-1.1.5+dfsg/minixml.h000066400000000000000000000047511261774340000160710ustar00rootroot00000000000000/* minimal xml parser * * Project : miniupnp * Website : http://miniupnp.free.fr/ * Author : Thomas Bernard * * Copyright (c) 2005, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __MINIXML_H__ #define __MINIXML_H__ #include #define IS_WHITE_SPACE(c) ((c==' ') || (c=='\t') || (c=='\r') || (c=='\n')) /* if a callback function pointer is set to NULL, * the function is not called */ struct xmlparser { const char *xmlstart; const char *xmlend; const char *xml; /* pointer to current character */ int xmlsize; uint32_t flags; void * data; void (*starteltfunc) (void *, const char *, int); void (*endeltfunc) (void *, const char *, int); void (*datafunc) (void *, const char *, int); void (*attfunc) (void *, const char *, int, const char *, int); }; /* parsexml() * the xmlparser structure must be initialized before the call * the following structure members have to be initialized : * xmlstart, xmlsize, data, *func * xml is for internal usage, xmlend is computed automatically */ void parsexml(struct xmlparser *); #endif minidlna-1.1.5+dfsg/options.c000066400000000000000000000123141261774340000160740ustar00rootroot00000000000000/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * author: Ryan Wagoner * * Copyright (c) 2006, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include "options.h" #include "utils.h" #include "upnpglobalvars.h" struct option * ary_options = NULL; int num_options = 0; static const struct { enum upnpconfigoptions id; const char * name; } optionids[] = { { UPNPIFNAME, "network_interface" }, { UPNPPORT, "port" }, { UPNPPRESENTATIONURL, "presentation_url" }, { UPNPNOTIFY_INTERVAL, "notify_interval" }, { UPNPUUID, "uuid"}, { UPNPSERIAL, "serial"}, { UPNPMODEL_NAME, "model_name"}, { UPNPMODEL_NUMBER, "model_number"}, { UPNPFRIENDLYNAME, "friendly_name"}, { UPNPMEDIADIR, "media_dir"}, { UPNPALBUMART_NAMES, "album_art_names"}, { UPNPINOTIFY, "inotify" }, { UPNPDBDIR, "db_dir" }, { UPNPLOGDIR, "log_dir" }, { UPNPLOGLEVEL, "log_level" }, { UPNPMINISSDPDSOCKET, "minissdpdsocket"}, { ENABLE_TIVO, "enable_tivo" }, { ENABLE_DLNA_STRICT, "strict_dlna" }, { ROOT_CONTAINER, "root_container" }, { USER_ACCOUNT, "user" }, { FORCE_SORT_CRITERIA, "force_sort_criteria" }, { MAX_CONNECTIONS, "max_connections" }, { MERGE_MEDIA_DIRS, "merge_media_dirs" } }; int readoptionsfile(const char * fname) { FILE *hfile = NULL; char buffer[1024]; char *equals; char *name; char *value; char *t; int linenum = 0; int i; enum upnpconfigoptions id; if(!fname || *fname == '\0') return -1; memset(buffer, 0, sizeof(buffer)); #ifdef DEBUG printf("Reading configuration from file %s\n", fname); #endif if(!(hfile = fopen(fname, "r"))) return -1; while(fgets(buffer, sizeof(buffer), hfile)) { linenum++; t = strchr(buffer, '\n'); if(t) { *t = '\0'; t--; while((t >= buffer) && isspace(*t)) { *t = '\0'; t--; } } /* skip leading whitespaces */ name = buffer; while(isspace(*name)) name++; /* check for comments or empty lines */ if(name[0] == '#' || name[0] == '\0') continue; if(!(equals = strchr(name, '='))) { fprintf(stderr, "parsing error file %s line %d : %s\n", fname, linenum, name); continue; } /* remove ending whitespaces */ for(t=equals-1; t>name && isspace(*t); t--) *t = '\0'; *equals = '\0'; value = equals+1; /* skip leading whitespaces */ while(isspace(*value)) value++; id = UPNP_INVALID; for(i=0; ipath); last_path = media_path; media_path = media_path->next; free(last_path); } art_names = album_art_names; while (art_names) { free(art_names->name); last_name = art_names; art_names = art_names->next; free(last_name); } if(ary_options) { free(ary_options); ary_options = NULL; num_options = 0; } } minidlna-1.1.5+dfsg/options.h000066400000000000000000000066321261774340000161070ustar00rootroot00000000000000/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * author: Ryan Wagoner * * Copyright (c) 2006, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __OPTIONS_H__ #define __OPTIONS_H__ #include "config.h" /* enum of option available in the miniupnpd.conf */ enum upnpconfigoptions { UPNP_INVALID = 0, UPNPIFNAME = 1, /* ext_ifname */ UPNPPORT, /* port */ UPNPPRESENTATIONURL, /* presentation_url */ UPNPNOTIFY_INTERVAL, /* notify_interval */ UPNPUUID, /* uuid */ UPNPSERIAL, /* serial */ UPNPMODEL_NAME, /* model_name */ UPNPMODEL_NUMBER, /* model_number */ UPNPFRIENDLYNAME, /* how the system should show up to DLNA clients */ UPNPMEDIADIR, /* directory to search for UPnP-A/V content */ UPNPALBUMART_NAMES, /* list of '/'-delimited file names to check for album art */ UPNPINOTIFY, /* enable inotify on the media directories */ UPNPDBDIR, /* base directory to store the database and album art cache */ UPNPLOGDIR, /* base directory to store the log file */ UPNPLOGLEVEL, /* logging verbosity */ UPNPMINISSDPDSOCKET, /* minissdpdsocket */ ENABLE_TIVO, /* enable support for streaming images and music to TiVo */ ENABLE_DLNA_STRICT, /* strictly adhere to DLNA specs */ ROOT_CONTAINER, /* root ObjectID (instead of "0") */ USER_ACCOUNT, /* user account to run as */ FORCE_SORT_CRITERIA, /* force sorting by a given sort criteria */ MAX_CONNECTIONS, /* maximum number of simultaneous connections */ MERGE_MEDIA_DIRS /* don't add an extra directory level when there are multiple media dirs */ }; /* readoptionsfile() * parse and store the option file values * returns: 0 success, -1 failure */ int readoptionsfile(const char * fname); /* freeoptions() * frees memory allocated to option values */ void freeoptions(void); #define MAX_OPTION_VALUE_LEN (200) struct option { enum upnpconfigoptions id; char value[MAX_OPTION_VALUE_LEN]; }; extern struct option * ary_options; extern int num_options; #endif minidlna-1.1.5+dfsg/playlist.c000066400000000000000000000150431261774340000162440ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2009-2010 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "tagutils/tagutils.h" #include "upnpglobalvars.h" #include "scanner.h" #include "metadata.h" #include "utils.h" #include "sql.h" #include "log.h" int insert_playlist(const char * path, char * name) { struct song_metadata plist; struct stat file; int items = 0, matches, ret; char type[4]; strncpyt(type, strrchr(name, '.')+1, 4); if( start_plist(path, NULL, &file, NULL, type) != 0 ) { DPRINTF(E_WARN, L_SCANNER, "Bad playlist [%s]\n", path); return -1; } while( (ret = next_plist_track(&plist, &file, NULL, type)) == 0 ) { items++; freetags(&plist); } if( ret == 2 ) // Bad playlist -- contains binary characters { DPRINTF(E_WARN, L_SCANNER, "Bad playlist [%s]\n", path); return -1; } strip_ext(name); DPRINTF(E_DEBUG, L_SCANNER, "Playlist %s contains %d items\n", name, items); matches = sql_get_int_field(db, "SELECT count(*) from PLAYLISTS where NAME = '%q'", name); if( matches > 0 ) { sql_exec(db, "INSERT into PLAYLISTS" " (NAME, PATH, ITEMS) " "VALUES" " ('%q(%d)', '%q', %d)", name, matches, path, items); } else { sql_exec(db, "INSERT into PLAYLISTS" " (NAME, PATH, ITEMS) " "VALUES" " ('%q', '%q', %d)", name, path, items); } return 0; } static unsigned int gen_dir_hash(const char *path) { char dir[PATH_MAX], *base; int len; strncpy(dir, path, sizeof(dir)); dir[sizeof(dir)-1] = '\0'; base = strrchr(dir, '/'); if( !base ) base = strrchr(dir, '\\'); if( base ) { *base = '\0'; len = base - dir; } else return 0; return DJBHash((uint8_t *)dir, len); } int fill_playlists(void) { int rows, i, found, len; char **result; char *plpath, *plname, *fname, *last_dir; unsigned int hash, last_hash = 0; char class[] = "playlistContainer"; struct song_metadata plist; struct stat file; char type[4]; int64_t plID, detailID; char sql_buf[] = "SELECT ID, NAME, PATH from PLAYLISTS where ITEMS > FOUND"; DPRINTF(E_WARN, L_SCANNER, "Parsing playlists...\n"); if( sql_get_table(db, sql_buf, &result, &rows, NULL) != SQLITE_OK ) return -1; if( !rows ) goto done; rows++; for( i=3; i 0 ) { found: DPRINTF(E_DEBUG, L_SCANNER, "+ %s found in db\n", fname); sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, NAME, REF_ID) " "SELECT" " '%s$%llX$%d', '%s$%llX', CLASS, DETAIL_ID, NAME, OBJECT_ID from OBJECTS" " where DETAIL_ID = %lld and OBJECT_ID glob '" BROWSEDIR_ID "$*'", MUSIC_PLIST_ID, plID, plist.track, MUSIC_PLIST_ID, plID, detailID); if( !last_dir ) { last_dir = sql_get_text_field(db, "SELECT PATH from DETAILS where ID = %lld", detailID); if( last_dir ) { fname = strrchr(last_dir, '/'); if( fname ) *fname = '\0'; } last_hash = hash; } found++; } else { DPRINTF(E_DEBUG, L_SCANNER, "- %s not found in db\n", fname); if( strchr(fname, '\\') ) { fname = modifyString(fname, "\\", "/", 1); goto retry; } else if( (fname = strchr(fname, '/')) ) { fname++; goto retry; } } freetags(&plist); } if( last_dir ) { sqlite3_free(last_dir); last_dir = NULL; } sql_exec(db, "UPDATE PLAYLISTS set FOUND = %d where ID = %lld", found, plID); } done: sqlite3_free_table(result); DPRINTF(E_WARN, L_SCANNER, "Finished parsing playlists.\n"); return 0; } minidlna-1.1.5+dfsg/playlist.h000066400000000000000000000017051261774340000162510ustar00rootroot00000000000000/* Playlist handling * * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Justin Maggard * * MiniDLNA media server * Copyright (C) 2008-2010 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #ifndef __PLAYLIST_H__ #define __PLAYLIST_H__ int insert_playlist(const char * path, char * name); int fill_playlists(void); #endif // __PLAYLIST_H__ minidlna-1.1.5+dfsg/po/000077500000000000000000000000001261774340000146525ustar00rootroot00000000000000minidlna-1.1.5+dfsg/po/LINGUAS000066400000000000000000000000471261774340000157000ustar00rootroot00000000000000da de es fr it ja ko nb nl pl ru sl sv minidlna-1.1.5+dfsg/po/Makevars000066400000000000000000000034351261774340000163530ustar00rootroot00000000000000# Makefile variables for PO directory in any package using GNU gettext. # Usually the message domain is the same as the package name. DOMAIN = $(PACKAGE) # These two variables depend on the location of this directory. subdir = po top_builddir = .. # These options get passed to xgettext. XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ # This is the copyright holder that gets inserted into the header of the # $(DOMAIN).pot file. Set this to the copyright holder of the surrounding # package. (Note that the msgstr strings, extracted from the package's # sources, belong to the copyright holder of the package.) Translators are # expected to transfer the copyright for their translations to this person # or entity, or to disclaim their copyright. The empty string stands for # the public domain; in this case the translators are expected to disclaim # their copyright. COPYRIGHT_HOLDER = Justin Maggard # This is the email address or URL to which the translators shall report # bugs in the untranslated strings: # - Strings which are not entire sentences, see the maintainer guidelines # in the GNU gettext documentation, section 'Preparing Strings'. # - Strings which use unclear terms or require additional context to be # understood. # - Strings which make invalid assumptions about notation of date, time or # money. # - Pluralisation problems. # - Incorrect English spelling. # - Incorrect formatting. # It can be your email address, or a mailing list address where translators # can write to without being subscribed, or the URL of a web page through # which the translators can contact you. MSGID_BUGS_ADDRESS = jmaggard@users.sourceforge.net # This is the list of locale categories, beyond LC_MESSAGES, for which the # message catalogs shall be used. It is usually empty. EXTRA_LOCALE_CATEGORIES = minidlna-1.1.5+dfsg/po/POTFILES.in000066400000000000000000000000121261774340000164200ustar00rootroot00000000000000scanner.c minidlna-1.1.5+dfsg/po/da.po000066400000000000000000000037231261774340000156030ustar00rootroot00000000000000# MiniDLNA translation template file # Copyright (C) 2010 NETGEAR # This file is distributed under the same license as the MiniDLNA package. # Justin Maggard , 2010. # msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: 2010-08-09 17:00-0700\n" "Last-Translator: ljensen \n" "Language-Team: Danish\n" "Language: da\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: scanner.c:165 msgid "Unknown Date" msgstr "Ukendt Dato" #: scanner.c:167 msgid "Unknown Camera" msgstr "Ukendt kamera" #: scanner.c:277 msgid "- All Albums -" msgstr "- Alle Album -" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "Ukendt Album" #: scanner.c:318 msgid "- All Artists -" msgstr "- Alle kunstnere -" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "Ukendt Kunstner" #: scanner.c:528 msgid "Music" msgstr "Musik" #: scanner.c:529 msgid "All Music" msgstr "Al Musik" #: scanner.c:530 msgid "Genre" msgstr "Genre" #: scanner.c:531 msgid "Artist" msgstr "Kunstner" #: scanner.c:532 msgid "Album" msgstr "Album" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "Mapper" #: scanner.c:534 msgid "Playlists" msgstr "Afspilningslister" #: scanner.c:536 msgid "Video" msgstr "Film" #: scanner.c:537 msgid "All Video" msgstr "Alle Film" #: scanner.c:540 msgid "Pictures" msgstr "Billeder" #: scanner.c:541 msgid "All Pictures" msgstr "Alle Billeder" #: scanner.c:542 msgid "Date Taken" msgstr "Foto-dato" #: scanner.c:543 msgid "Camera" msgstr "Kamera" #: scanner.c:546 msgid "Browse Folders" msgstr "Vise Mapper" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "Søger %s\n" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "Søgning %s slut (%llu filer)!\n" minidlna-1.1.5+dfsg/po/de.po000066400000000000000000000050571261774340000156110ustar00rootroot00000000000000# MiniDLNA translation template file # Justin Maggard , 2010. # # MiniDLNA media server # Copyright (C) 2010 NETGEAR # # This file is part of MiniDLNA. # # MiniDLNA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # MiniDLNA 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 MiniDLNA. If not, see . # msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: 2010-10-19 11:00-0800\n" "Last-Translator: Andi Miko \n" "Language-Team: German\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: scanner.c:165 msgid "Unknown Date" msgstr "Unbekanntes Datum" #: scanner.c:167 msgid "Unknown Camera" msgstr "Unbekannte Kamera" #: scanner.c:277 msgid "- All Albums -" msgstr "- Alle Alben -" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "Unbekanntes Album" #: scanner.c:318 msgid "- All Artists -" msgstr "- Alle Interpreten -" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "Unbekannter Interpret" #: scanner.c:528 msgid "Music" msgstr "Musik" #: scanner.c:529 msgid "All Music" msgstr "Alle Titel" #: scanner.c:530 msgid "Genre" msgstr "Genre" #: scanner.c:531 msgid "Artist" msgstr "Interpret" #: scanner.c:532 msgid "Album" msgstr "Album" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "Ordner" #: scanner.c:534 msgid "Playlists" msgstr "Wiedergabelisten" #: scanner.c:536 msgid "Video" msgstr "Video" #: scanner.c:537 msgid "All Video" msgstr "Alle Videos" #: scanner.c:540 msgid "Pictures" msgstr "Bilder" #: scanner.c:541 msgid "All Pictures" msgstr "Alle Bilder" #: scanner.c:542 msgid "Date Taken" msgstr "Aufnahmedatum" #: scanner.c:543 msgid "Camera" msgstr "Kamera" #: scanner.c:546 msgid "Browse Folders" msgstr "Ordner durchsuchen" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "%s wird gescannt ... \n" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "Scan von %s abgeschlossen (%llu files)!\n" minidlna-1.1.5+dfsg/po/es.po000066400000000000000000000040301261774340000156160ustar00rootroot00000000000000# MiniDLNA translation template file # Copyright (C) 2010 NETGEAR # This file is distributed under the same license as the MiniDLNA package. # Justin Maggard , 2010. # msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: 2010-08-09 17:00-0700\n" "Last-Translator: doubolplay1 \n" "Language-Team: Spanish\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: scanner.c:165 msgid "Unknown Date" msgstr "Fecha desconocida" #: scanner.c:167 msgid "Unknown Camera" msgstr "Cámara desconocida" #: scanner.c:277 msgid "- All Albums -" msgstr "- Álbumes -" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "Álbum desconocido" #: scanner.c:318 msgid "- All Artists -" msgstr "- Artistas -" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "Artista desconocido" #: scanner.c:528 msgid "Music" msgstr "Música" #: scanner.c:529 msgid "All Music" msgstr "Toda la música" #: scanner.c:530 msgid "Genre" msgstr "Género" #: scanner.c:531 msgid "Artist" msgstr "Artista" #: scanner.c:532 msgid "Album" msgstr "Álbum" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "Carpetas" #: scanner.c:534 msgid "Playlists" msgstr "Listas" #: scanner.c:536 msgid "Video" msgstr "Vídeo" #: scanner.c:537 msgid "All Video" msgstr "Todos los vídeos" #: scanner.c:540 msgid "Pictures" msgstr "Fotos" #: scanner.c:541 msgid "All Pictures" msgstr "Todas las fotos" #: scanner.c:542 msgid "Date Taken" msgstr "Fecha en que se hizo" #: scanner.c:543 msgid "Camera" msgstr "Cámara" #: scanner.c:546 msgid "Browse Folders" msgstr "Examinar carpetas" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "Explorando %s\n" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "Terminada la exploración de %s (%llu archivos)\n" minidlna-1.1.5+dfsg/po/fr.po000066400000000000000000000040411261774340000156200ustar00rootroot00000000000000# MiniDLNA translation template file # Copyright (C) 2010 NETGEAR # This file is distributed under the same license as the MiniDLNA package. # Justin Maggard , 2010. # msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: 2013-06-12 16:56+0200\n" "Last-Translator: Benoît Knecht \n" "Language-Team: French\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: scanner.c:165 msgid "Unknown Date" msgstr "Date inconnue" #: scanner.c:167 msgid "Unknown Camera" msgstr "Caméra inconnue" #: scanner.c:277 msgid "- All Albums -" msgstr "- Tous les albums -" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "Album inconnu" #: scanner.c:318 msgid "- All Artists -" msgstr "- Tous les artistes -" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "Artiste inconnu" #: scanner.c:528 msgid "Music" msgstr "Musique" #: scanner.c:529 msgid "All Music" msgstr "Toute la musique" #: scanner.c:530 msgid "Genre" msgstr "Genre" #: scanner.c:531 msgid "Artist" msgstr "Artiste" #: scanner.c:532 msgid "Album" msgstr "Album" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "Dossiers" #: scanner.c:534 msgid "Playlists" msgstr "Liste de lecture" #: scanner.c:536 msgid "Video" msgstr "Vidéo" #: scanner.c:537 msgid "All Video" msgstr "Toutes les vidéos" #: scanner.c:540 msgid "Pictures" msgstr "Images" #: scanner.c:541 msgid "All Pictures" msgstr "Toutes les images" #: scanner.c:542 msgid "Date Taken" msgstr "Date de prise" #: scanner.c:543 msgid "Camera" msgstr "Caméra" #: scanner.c:546 msgid "Browse Folders" msgstr "Parcourir les dossiers" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "Analyse de %s\n" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "Analyse de %s terminée (%llu fichiers) !\n" minidlna-1.1.5+dfsg/po/it.po000066400000000000000000000041331261774340000156270ustar00rootroot00000000000000# MiniDLNA translation template file # Copyright (C) 2010 NETGEAR # This file is distributed under the same license as the MiniDLNA package. # Justin Maggard , 2010. # msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: 2010-11-01 10:04+0100\n" "Last-Translator: Andrea Musuruane \n" "Language-Team: Italian\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-Language: Italian\n" "X-Poedit-Country: ITALY\n" #: scanner.c:165 msgid "Unknown Date" msgstr "Data sconosciuta" #: scanner.c:167 msgid "Unknown Camera" msgstr "Fotocamera sconosciuta" #: scanner.c:277 msgid "- All Albums -" msgstr "- Tutti gli album -" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "Album sconosciuto" #: scanner.c:318 msgid "- All Artists -" msgstr "- Tutti gli artisti -" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "Artista sconosciuto" #: scanner.c:528 msgid "Music" msgstr "Musica" #: scanner.c:529 msgid "All Music" msgstr "Tutta la musica" #: scanner.c:530 msgid "Genre" msgstr "Genere" #: scanner.c:531 msgid "Artist" msgstr "Artista" #: scanner.c:532 msgid "Album" msgstr "Album" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "Cartelle" #: scanner.c:534 msgid "Playlists" msgstr "Scalette" #: scanner.c:536 msgid "Video" msgstr "Video" #: scanner.c:537 msgid "All Video" msgstr "Tutti i video" #: scanner.c:540 msgid "Pictures" msgstr "Immagini" #: scanner.c:541 msgid "All Pictures" msgstr "Tutte le immagini" #: scanner.c:542 msgid "Date Taken" msgstr "Data dello scatto" #: scanner.c:543 msgid "Camera" msgstr "Fotocamera" #: scanner.c:546 msgid "Browse Folders" msgstr "Esplora cartelle" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "Scansione di %s\n" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "Scansione di %s finita (%llu file)\n" minidlna-1.1.5+dfsg/po/ja.po000066400000000000000000000041631261774340000156100ustar00rootroot00000000000000# MiniDLNA translation template file # Copyright (C) 2010 NETGEAR # This file is distributed under the same license as the MiniDLNA package. # Justin Maggard , 2010. # msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: 2010-07-23 15:57-0800\n" "Last-Translator: r2d2 \n" "Language-Team: Japanese\n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: scanner.c:165 msgid "Unknown Date" msgstr "日付不明" #: scanner.c:167 msgid "Unknown Camera" msgstr "カメラ名不明" #: scanner.c:277 msgid "- All Albums -" msgstr "- すべてのアルバム -" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "アルバム名不明" #: scanner.c:318 msgid "- All Artists -" msgstr "- すべてのアーティスト -" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "アーティスト名不明" #: scanner.c:528 msgid "Music" msgstr "ミュージック" #: scanner.c:529 msgid "All Music" msgstr "すべてのミュージック" #: scanner.c:530 msgid "Genre" msgstr "ジャンル" #: scanner.c:531 msgid "Artist" msgstr "アーティスト" #: scanner.c:532 msgid "Album" msgstr "アルバム" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "フォルダ" #: scanner.c:534 msgid "Playlists" msgstr "プレイリスト" #: scanner.c:536 msgid "Video" msgstr "ビデオ" #: scanner.c:537 msgid "All Video" msgstr "すべてのビデオ" #: scanner.c:540 msgid "Pictures" msgstr "写真" #: scanner.c:541 msgid "All Pictures" msgstr "すべての写真" #: scanner.c:542 msgid "Date Taken" msgstr "撮影日" #: scanner.c:543 msgid "Camera" msgstr "カメラ" #: scanner.c:546 msgid "Browse Folders" msgstr "フォルダ" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "%s を検索中\n" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "%s (%llu ファイル) の検索終了!\n" minidlna-1.1.5+dfsg/po/ko.po000066400000000000000000000040321261774340000156220ustar00rootroot00000000000000# MiniDLNA translation template file # Copyright (C) 2010 NETGEAR # This file is distributed under the same license as the MiniDLNA package. # Justin Maggard , 2010. # msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: 2010-08-09 17:00-0700\n" "Last-Translator: mangg \n" "Language-Team: Korean\n" "Language: ko\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: scanner.c:165 msgid "Unknown Date" msgstr "알수 없는 날짜" #: scanner.c:167 msgid "Unknown Camera" msgstr "알수 없는 카메라" #: scanner.c:277 msgid "- All Albums -" msgstr "-모든 앨범-" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "알수 없는 앨범" #: scanner.c:318 msgid "- All Artists -" msgstr "- 모든 아티스트 -" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "알수없는 아티스트" #: scanner.c:528 msgid "Music" msgstr "음악" #: scanner.c:529 msgid "All Music" msgstr "모든 음악" #: scanner.c:530 msgid "Genre" msgstr "장르" #: scanner.c:531 msgid "Artist" msgstr "아티스트" #: scanner.c:532 msgid "Album" msgstr "앨범" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "폴더" #: scanner.c:534 msgid "Playlists" msgstr "재생 목록" #: scanner.c:536 msgid "Video" msgstr "비디오" #: scanner.c:537 msgid "All Video" msgstr "모든 비디오" #: scanner.c:540 msgid "Pictures" msgstr "사진" #: scanner.c:541 msgid "All Pictures" msgstr "모든 사진" #: scanner.c:542 msgid "Date Taken" msgstr "촬영 날짜" #: scanner.c:543 msgid "Camera" msgstr "카메라" #: scanner.c:546 msgid "Browse Folders" msgstr "폴더 보기" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "검색중 %s\n" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "%s 검색 종료. (%llu 파일)\n" minidlna-1.1.5+dfsg/po/minidlna.pot000066400000000000000000000044301261774340000171720ustar00rootroot00000000000000# MiniDLNA translation template file # Justin Maggard , 2010. # # MiniDLNA media server # Copyright (C) 2010 NETGEAR # # This file is part of MiniDLNA. # # MiniDLNA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # MiniDLNA 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 MiniDLNA. If not, see . # #, fuzzy msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: scanner.c:165 msgid "Unknown Date" msgstr "" #: scanner.c:167 msgid "Unknown Camera" msgstr "" #: scanner.c:277 msgid "- All Albums -" msgstr "" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "" #: scanner.c:318 msgid "- All Artists -" msgstr "" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "" #: scanner.c:528 msgid "Music" msgstr "" #: scanner.c:529 msgid "All Music" msgstr "" #: scanner.c:530 msgid "Genre" msgstr "" #: scanner.c:531 msgid "Artist" msgstr "" #: scanner.c:532 msgid "Album" msgstr "" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "" #: scanner.c:534 msgid "Playlists" msgstr "" #: scanner.c:536 msgid "Video" msgstr "" #: scanner.c:537 msgid "All Video" msgstr "" #: scanner.c:540 msgid "Pictures" msgstr "" #: scanner.c:541 msgid "All Pictures" msgstr "" #: scanner.c:542 msgid "Date Taken" msgstr "" #: scanner.c:543 msgid "Camera" msgstr "" #: scanner.c:546 msgid "Browse Folders" msgstr "" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "" minidlna-1.1.5+dfsg/po/nb.po000066400000000000000000000037151261774340000156170ustar00rootroot00000000000000# MiniDLNA translation template file # Copyright (C) 2010 NETGEAR # This file is distributed under the same license as the MiniDLNA package. # Justin Maggard , 2010. # msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: 2010-08-09 17:00-0700\n" "Last-Translator: samundsen \n" "Language-Team: Norwegian\n" "Language: nb\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: scanner.c:165 msgid "Unknown Date" msgstr "Ukjent dato" #: scanner.c:167 msgid "Unknown Camera" msgstr "Ukjent kamera" #: scanner.c:277 msgid "- All Albums -" msgstr "- Alle album -" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "Ukjent album" #: scanner.c:318 msgid "- All Artists -" msgstr "- Alle artister -" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "Ukjent artist" #: scanner.c:528 msgid "Music" msgstr "Musikk" #: scanner.c:529 msgid "All Music" msgstr "All musikk" #: scanner.c:530 msgid "Genre" msgstr "Genre" #: scanner.c:531 msgid "Artist" msgstr "Artist" #: scanner.c:532 msgid "Album" msgstr "Album" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "Mapper" #: scanner.c:534 msgid "Playlists" msgstr "Spillelister" #: scanner.c:536 msgid "Video" msgstr "Video" #: scanner.c:537 msgid "All Video" msgstr "Alle videoer" #: scanner.c:540 msgid "Pictures" msgstr "Bilder" #: scanner.c:541 msgid "All Pictures" msgstr "Alle bilder" #: scanner.c:542 msgid "Date Taken" msgstr "Fotodato" #: scanner.c:543 msgid "Camera" msgstr "Kamera" #: scanner.c:546 msgid "Browse Folders" msgstr "Vis mapper" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "Søker %s\n" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "Søk %s ferdig (%llu filer)!\n" minidlna-1.1.5+dfsg/po/nl.po000066400000000000000000000037751261774340000156370ustar00rootroot00000000000000# MiniDLNA translation template file # Copyright (C) 2010 NETGEAR # This file is distributed under the same license as the MiniDLNA package. # Justin Maggard , 2010. # msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: 2010-08-09 17:00-0700\n" "Last-Translator: frejac \n" "Language-Team: Swedish\n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: scanner.c:165 msgid "Unknown Date" msgstr "Datum onbekend" #: scanner.c:167 msgid "Unknown Camera" msgstr "Camera onbekend" #: scanner.c:277 msgid "- All Albums -" msgstr "- Alle albums -" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "Album onbekend" #: scanner.c:318 msgid "- All Artists -" msgstr "- Alle artiesten -" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "Artiest onbekend" #: scanner.c:528 msgid "Music" msgstr "Muziek" #: scanner.c:529 msgid "All Music" msgstr "Alle muziek bestanden" #: scanner.c:530 msgid "Genre" msgstr "Genre" #: scanner.c:531 msgid "Artist" msgstr "Artiest" #: scanner.c:532 msgid "Album" msgstr "Album" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "Mappen" #: scanner.c:534 msgid "Playlists" msgstr "Afspeellijst" #: scanner.c:536 msgid "Video" msgstr "Video" #: scanner.c:537 msgid "All Video" msgstr "Alle video bestanden" #: scanner.c:540 msgid "Pictures" msgstr "Foto's" #: scanner.c:541 msgid "All Pictures" msgstr "Alle foto bestanden" #: scanner.c:542 msgid "Date Taken" msgstr "Opname datum" #: scanner.c:543 msgid "Camera" msgstr "Camera" #: scanner.c:546 msgid "Browse Folders" msgstr "Mappen doorzoeken" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "Zoeken %s\n" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "Zoeken %s gereed (%llu files)!\n" minidlna-1.1.5+dfsg/po/pl.po000066400000000000000000000051101261774340000156220ustar00rootroot00000000000000# MiniDLNA translation template file # Justin Maggard , 2010. # # MiniDLNA media server # Copyright (C) 2010 NETGEAR # # This file is part of MiniDLNA. # # MiniDLNA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # MiniDLNA 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 MiniDLNA. If not, see . # msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: 2010-10-19 11:00-0800\n" "Last-Translator: Tomasz.Matuszewski \n" "Language-Team: Polski\n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: scanner.c:165 msgid "Unknown Date" msgstr "Nieznana Data" #: scanner.c:167 msgid "Unknown Camera" msgstr "Nieznana Kamera" #: scanner.c:277 msgid "- All Albums -" msgstr "- Wszystkie Albumy -" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "Nieznany Album" #: scanner.c:318 msgid "- All Artists -" msgstr "- Wszyscy Wykonawcy -" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "Nieznany Wykonawca" #: scanner.c:528 msgid "Music" msgstr "Muzyka" #: scanner.c:529 msgid "All Music" msgstr "Wszystkie Utwory" #: scanner.c:530 msgid "Genre" msgstr "Rodzaj" #: scanner.c:531 msgid "Artist" msgstr "Wykonawca" #: scanner.c:532 msgid "Album" msgstr "Album" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "Folder" #: scanner.c:534 msgid "Playlists" msgstr "Lista Utworow" #: scanner.c:536 msgid "Video" msgstr "Filmy" #: scanner.c:537 msgid "All Video" msgstr "Wszystkie Filmy" #: scanner.c:540 msgid "Pictures" msgstr "Obrazy" #: scanner.c:541 msgid "All Pictures" msgstr "Wszystkie Obrazy" #: scanner.c:542 msgid "Date Taken" msgstr "Data Wykonania" #: scanner.c:543 msgid "Camera" msgstr "Kamera" #: scanner.c:546 msgid "Browse Folders" msgstr "Przegladaj Foldery" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "Skanowanie %s\n" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "Skanowanie %s zakonczone (%llu files)!\n" minidlna-1.1.5+dfsg/po/ru.po000066400000000000000000000056111261774340000156430ustar00rootroot00000000000000# MiniDLNA translation template file # Justin Maggard , 2010. # # MiniDLNA media server # Copyright (C) 2010 NETGEAR # # This file is part of MiniDLNA. # # MiniDLNA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # MiniDLNA 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 MiniDLNA. If not, see . # msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: 2011-03-25 22:40+0500\n" "Last-Translator: Ivan Mironov \n" "Language-Team: \n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-Language: Russian\n" "X-Poedit-Country: RUSSIAN FEDERATION\n" #: scanner.c:165 msgid "Unknown Date" msgstr "Неизвестная Дата" #: scanner.c:167 msgid "Unknown Camera" msgstr "Неизвестная Камера" #: scanner.c:277 msgid "- All Albums -" msgstr "- Все Альбомы -" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "Неизвестный Альбом" #: scanner.c:318 msgid "- All Artists -" msgstr "- Все Исполнители -" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "Неизвестный Исполнитель" #: scanner.c:528 msgid "Music" msgstr "Музыка" #: scanner.c:529 msgid "All Music" msgstr "Вся Музыка" #: scanner.c:530 msgid "Genre" msgstr "Жанр" #: scanner.c:531 msgid "Artist" msgstr "Исполнитель" #: scanner.c:532 msgid "Album" msgstr "Альбом" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "Папки" #: scanner.c:534 msgid "Playlists" msgstr "Списки Воспроизведения" #: scanner.c:536 msgid "Video" msgstr "Видео" #: scanner.c:537 msgid "All Video" msgstr "Всё Видео" #: scanner.c:540 msgid "Pictures" msgstr "Фотографии" #: scanner.c:541 msgid "All Pictures" msgstr "Все Фотографии" #: scanner.c:542 msgid "Date Taken" msgstr "Дата Съёмки" #: scanner.c:543 msgid "Camera" msgstr "Камера" #: scanner.c:546 msgid "Browse Folders" msgstr "Просмотреть Папки" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "Сканирование %s\n" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "Сканирование %s завершено (%llu файлов)!\n" minidlna-1.1.5+dfsg/po/sl.po000066400000000000000000000050241261774340000156310ustar00rootroot00000000000000# MiniDLNA translation template file # Justin Maggard , 2010. # # MiniDLNA media server # Copyright (C) 2010 NETGEAR # # This file is part of MiniDLNA. # # MiniDLNA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # MiniDLNA 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 MiniDLNA. If not, see . # msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: 2010-10-19 11:00-0800\n" "Last-Translator: Bojan.Krstic \n" "Language-Team: Slovenski\n" "Language: sl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: scanner.c:165 msgid "Unknown Date" msgstr "Neznan Datum" #: scanner.c:167 msgid "Unknown Camera" msgstr "Neznana Kamera" #: scanner.c:277 msgid "- All Albums -" msgstr "- Vsi Albumi -" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "Neznan Album" #: scanner.c:318 msgid "- All Artists -" msgstr "- Vsi Izvajalci -" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "Neznan Izvajalec" #: scanner.c:528 msgid "Music" msgstr "Glasba" #: scanner.c:529 msgid "All Music" msgstr "Vsa Glasba" #: scanner.c:530 msgid "Genre" msgstr "Zvrst" #: scanner.c:531 msgid "Artist" msgstr "Izvajalec" #: scanner.c:532 msgid "Album" msgstr "Album" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "Mape" #: scanner.c:534 msgid "Playlists" msgstr "Seznami predvajanj" #: scanner.c:536 msgid "Video" msgstr "Video" #: scanner.c:537 msgid "All Video" msgstr "Vsi Videi" #: scanner.c:540 msgid "Pictures" msgstr "Slike" #: scanner.c:541 msgid "All Pictures" msgstr "Vse Slike" #: scanner.c:542 msgid "Date Taken" msgstr "Datum Posnetka" #: scanner.c:543 msgid "Camera" msgstr "Kamera" #: scanner.c:546 msgid "Browse Folders" msgstr "Prebrskaj Mape" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "Skeniranje %s\n" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "Skeniranje %s konèano (%llu files)!\n" minidlna-1.1.5+dfsg/po/sv.po000066400000000000000000000037271261774340000156530ustar00rootroot00000000000000# MiniDLNA translation template file # Copyright (C) 2010 NETGEAR # This file is distributed under the same license as the MiniDLNA package. # Justin Maggard , 2010. # msgid "" msgstr "" "Project-Id-Version: minidlna 1.1.0\n" "Report-Msgid-Bugs-To: jmaggard@users.sourceforge.net\n" "POT-Creation-Date: 2013-06-12 16:46+0200\n" "PO-Revision-Date: 2010-08-09 17:00-0700\n" "Last-Translator: frejac \n" "Language-Team: Swedish\n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: scanner.c:165 msgid "Unknown Date" msgstr "Okänt datum" #: scanner.c:167 msgid "Unknown Camera" msgstr "Okänd kamera" #: scanner.c:277 msgid "- All Albums -" msgstr "- Alla album -" #: scanner.c:285 scanner.c:292 scanner.c:296 msgid "Unknown Album" msgstr "Okänt album" #: scanner.c:318 msgid "- All Artists -" msgstr "- Alla artister -" #: scanner.c:326 scanner.c:332 scanner.c:336 msgid "Unknown Artist" msgstr "Okänd artist" #: scanner.c:528 msgid "Music" msgstr "Musik" #: scanner.c:529 msgid "All Music" msgstr "All Musik" #: scanner.c:530 msgid "Genre" msgstr "Genre" #: scanner.c:531 msgid "Artist" msgstr "Artist" #: scanner.c:532 msgid "Album" msgstr "Album" #: scanner.c:533 scanner.c:538 scanner.c:544 msgid "Folders" msgstr "Mappar" #: scanner.c:534 msgid "Playlists" msgstr "Spelningslistor" #: scanner.c:536 msgid "Video" msgstr "Film" #: scanner.c:537 msgid "All Video" msgstr "Alla filmer" #: scanner.c:540 msgid "Pictures" msgstr "Bilder" #: scanner.c:541 msgid "All Pictures" msgstr "Alla bilder" #: scanner.c:542 msgid "Date Taken" msgstr "Fotodatum" #: scanner.c:543 msgid "Camera" msgstr "Kamera" #: scanner.c:546 msgid "Browse Folders" msgstr "Utforska mappar" #: scanner.c:690 #, c-format msgid "Scanning %s\n" msgstr "Söker %s\n" #: scanner.c:766 #, c-format msgid "Scanning %s finished (%llu files)!\n" msgstr "Avsökning %s slutförd (%llu filer)!\n" minidlna-1.1.5+dfsg/process.c000066400000000000000000000106011261774340000160540ustar00rootroot00000000000000/* Process handling * * Copyright © 2006, Thomas Bernard * Copyright © 2013, Benoît Knecht * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include "upnpglobalvars.h" #include "process.h" #include "config.h" #include "log.h" struct child *children = NULL; int number_of_children = 0; static void add_process_info(pid_t pid, struct client_cache_s *client) { struct child *child; int i; for (i = 0; i < runtime_vars.max_connections; i++) { child = children+i; if (child->pid) continue; child->pid = pid; child->client = client; child->age = time(NULL); break; } } static inline void remove_process_info(pid_t pid) { struct child *child; int i; for (i = 0; i < runtime_vars.max_connections; i++) { child = children+i; if (child->pid != pid) continue; child->pid = 0; if (child->client) child->client->connections--; break; } } pid_t process_fork(struct client_cache_s *client) { if (number_of_children >= runtime_vars.max_connections) { DPRINTF(E_WARN, L_GENERAL, "Exceeded max connections [%d], not forking\n", runtime_vars.max_connections); errno = EAGAIN; return -1; } pid_t pid = fork(); if (pid > 0) { number_of_children++; if (client) client->connections++; add_process_info(pid, client); } return pid; } void process_handle_child_termination(int signal) { pid_t pid; while ((pid = waitpid(-1, NULL, WNOHANG))) { if (pid == -1) { if (errno == EINTR) continue; else break; } number_of_children--; remove_process_info(pid); } } int process_daemonize(void) { int pid; #ifndef USE_DAEMON int i; switch(fork()) { /* fork error */ case -1: perror("fork()"); exit(1); /* child process */ case 0: /* obtain a new process group */ if( (pid = setsid()) < 0) { perror("setsid()"); exit(1); } /* close all descriptors */ for (i=getdtablesize();i>=0;--i) close(i); i = open("/dev/null",O_RDWR); /* open stdin */ dup(i); /* stdout */ dup(i); /* stderr */ umask(027); chdir("/"); break; /* parent process */ default: exit(0); } #else if( daemon(0, 0) < 0 ) perror("daemon()"); pid = getpid(); #endif return pid; } int process_check_if_running(const char *fname) { char buffer[64]; int pidfile; pid_t pid; if(!fname || *fname == '\0') return -1; if( (pidfile = open(fname, O_RDONLY)) < 0) return 0; memset(buffer, 0, 64); if(read(pidfile, buffer, 63) > 0) { if( (pid = atol(buffer)) > 0) { if(!kill(pid, 0)) { close(pidfile); return -2; } } } close(pidfile); return 0; } void process_reap_children(void) { struct child *child; int i; for (i = 0; i < runtime_vars.max_connections; i++) { child = children+i; if (child->pid) kill(child->pid, SIGKILL); } } minidlna-1.1.5+dfsg/process.h000066400000000000000000000057371261774340000160770ustar00rootroot00000000000000/* Process handling * * Copyright © 2013, Benoît Knecht * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __PROCESS_H__ #define __PROCESS_H__ #include #include "clients.h" struct child { pid_t pid; time_t age; struct client_cache_s *client; }; extern struct child *children; extern int number_of_children; /** * Fork a new child (just like fork()) but keep track of how many childs are * already running, and refuse fo fork if there are too many. * @return -1 if it couldn't fork, 0 in the child process, the pid of the * child process in the parent process. */ pid_t process_fork(struct client_cache_s *client); /** * Handler to be called upon receiving SIGCHLD. This signal is received by the * parent process when a child terminates, and this handler updates the number * of running childs accordingly. * @param signal The signal number. */ void process_handle_child_termination(int signal); /** * Daemonize the current process by forking itself and redirecting standard * input, standard output and standard error to /dev/null. * @return The pid of the process. */ int process_daemonize(void); /** * Check if the process corresponding to the pid found in the pid file is * running. * @param fname The path to the pid file. * @return 0 if no other instance is running, -1 if the file name is invalid, * -2 if another instance is running. */ int process_check_if_running(const char *fname); /** * Kill all child processes */ void process_reap_children(void); #endif // __PROCESS_H__ minidlna-1.1.5+dfsg/scanner.c000066400000000000000000000703561261774340000160440ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #ifdef ENABLE_NLS #include #endif #include #include "libav.h" #include "scanner_sqlite.h" #include "upnpglobalvars.h" #include "metadata.h" #include "playlist.h" #include "utils.h" #include "sql.h" #include "scanner.h" #include "albumart.h" #include "containers.h" #include "log.h" #if SCANDIR_CONST typedef const struct dirent scan_filter; #else typedef struct dirent scan_filter; #endif #ifndef AV_LOG_PANIC #define AV_LOG_PANIC AV_LOG_FATAL #endif int valid_cache = 0; struct virtual_item { int64_t objectID; char parentID[64]; char name[256]; }; int64_t get_next_available_id(const char *table, const char *parentID) { char *ret, *base; int64_t objectID = 0; ret = sql_get_text_field(db, "SELECT OBJECT_ID from %s where ID = " "(SELECT max(ID) from %s where PARENT_ID = '%s')", table, table, parentID); if( ret ) { base = strrchr(ret, '$'); if( base ) objectID = strtoll(base+1, NULL, 16) + 1; sqlite3_free(ret); } return objectID; } int insert_container(const char *item, const char *rootParent, const char *refID, const char *class, const char *artist, const char *genre, const char *album_art, int64_t *objectID, int64_t *parentID) { char *result; char *base; int ret = 0; result = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS o " "left join DETAILS d on (o.DETAIL_ID = d.ID)" " where o.PARENT_ID = '%s'" " and o.NAME like '%q'" " and d.ARTIST %s %Q" " and o.CLASS = 'container.%s' limit 1", rootParent, item, artist?"like":"is", artist, class); if( result ) { base = strrchr(result, '$'); if( base ) *parentID = strtoll(base+1, NULL, 16); else *parentID = 0; *objectID = get_next_available_id("OBJECTS", result); } else { int64_t detailID = 0; *objectID = 0; *parentID = get_next_available_id("OBJECTS", rootParent); if( refID ) { result = sql_get_text_field(db, "SELECT DETAIL_ID from OBJECTS where OBJECT_ID = %Q", refID); if( result ) detailID = strtoll(result, NULL, 10); } if( !detailID ) { detailID = GetFolderMetadata(item, NULL, artist, genre, (album_art ? strtoll(album_art, NULL, 10) : 0)); } ret = sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " "VALUES" " ('%s$%llX', '%s', %Q, %lld, 'container.%s', '%q')", rootParent, (long long)*parentID, rootParent, refID, (long long)detailID, class, item); } sqlite3_free(result); return ret; } static void insert_containers(const char *name, const char *path, const char *refID, const char *class, int64_t detailID) { char sql[128]; char **result; int ret; int cols, row; int64_t objectID, parentID; if( strstr(class, "imageItem") ) { char *date_taken = NULL, *camera = NULL; static struct virtual_item last_date; static struct virtual_item last_cam; static struct virtual_item last_camdate; static long long last_all_objectID = 0; snprintf(sql, sizeof(sql), "SELECT DATE, CREATOR from DETAILS where ID = %lld", (long long)detailID); ret = sql_get_table(db, sql, &result, &row, &cols); if( ret == SQLITE_OK ) { date_taken = result[2]; camera = result[3]; } if( date_taken ) date_taken[10] = '\0'; else date_taken = _("Unknown Date"); if( !camera ) camera = _("Unknown Camera"); if( valid_cache && strcmp(last_date.name, date_taken) == 0 ) { last_date.objectID++; //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID); } else { insert_container(date_taken, IMAGE_DATE_ID, NULL, "album.photoAlbum", NULL, NULL, NULL, &objectID, &parentID); sprintf(last_date.parentID, IMAGE_DATE_ID"$%llX", (unsigned long long)parentID); last_date.objectID = objectID; strncpyt(last_date.name, date_taken, sizeof(last_date.name)); //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID); } sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", last_date.parentID, (long long)last_date.objectID, last_date.parentID, refID, class, (long long)detailID, name); if( !valid_cache || strcmp(camera, last_cam.name) != 0 ) { insert_container(camera, IMAGE_CAMERA_ID, NULL, "storageFolder", NULL, NULL, NULL, &objectID, &parentID); sprintf(last_cam.parentID, IMAGE_CAMERA_ID"$%llX", (long long)parentID); strncpyt(last_cam.name, camera, sizeof(last_cam.name)); /* Invalidate last_camdate cache */ last_camdate.name[0] = '\0'; } if( valid_cache && strcmp(last_camdate.name, date_taken) == 0 ) { last_camdate.objectID++; //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last camdate item: %s/%s/%s/%X\n", camera, last_camdate.name, last_camdate.parentID, last_camdate.objectID); } else { insert_container(date_taken, last_cam.parentID, NULL, "album.photoAlbum", NULL, NULL, NULL, &objectID, &parentID); sprintf(last_camdate.parentID, "%s$%llX", last_cam.parentID, (long long)parentID); last_camdate.objectID = objectID; strncpyt(last_camdate.name, date_taken, sizeof(last_camdate.name)); //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached camdate item: %s/%s/%s/%X\n", camera, last_camdate.name, last_camdate.parentID, last_camdate.objectID); } sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", last_camdate.parentID, last_camdate.objectID, last_camdate.parentID, refID, class, (long long)detailID, name); /* All Images */ if( !last_all_objectID ) { last_all_objectID = get_next_available_id("OBJECTS", IMAGE_ALL_ID); } sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" " ('"IMAGE_ALL_ID"$%llX', '"IMAGE_ALL_ID"', '%s', '%s', %lld, %Q)", last_all_objectID++, refID, class, (long long)detailID, name); } else if( strstr(class, "audioItem") ) { snprintf(sql, sizeof(sql), "SELECT ALBUM, ARTIST, GENRE, ALBUM_ART from DETAILS where ID = %lld", (long long)detailID); ret = sql_get_table(db, sql, &result, &row, &cols); if( ret != SQLITE_OK ) return; if( !row ) { sqlite3_free_table(result); return; } char *album = result[4], *artist = result[5], *genre = result[6]; char *album_art = result[7]; static struct virtual_item last_album; static struct virtual_item last_artist; static struct virtual_item last_artistAlbum; static struct virtual_item last_artistAlbumAll; static struct virtual_item last_genre; static struct virtual_item last_genreArtist; static struct virtual_item last_genreArtistAll; static long long last_all_objectID = 0; if( album ) { if( valid_cache && strcmp(album, last_album.name) == 0 ) { last_album.objectID++; //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID); } else { strncpyt(last_album.name, album, sizeof(last_album.name)); insert_container(album, MUSIC_ALBUM_ID, NULL, "album.musicAlbum", artist, genre, album_art, &objectID, &parentID); sprintf(last_album.parentID, MUSIC_ALBUM_ID"$%llX", (long long)parentID); last_album.objectID = objectID; //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID); } sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", last_album.parentID, last_album.objectID, last_album.parentID, refID, class, (long long)detailID, name); } if( artist ) { if( !valid_cache || strcmp(artist, last_artist.name) != 0 ) { insert_container(artist, MUSIC_ARTIST_ID, NULL, "person.musicArtist", NULL, genre, NULL, &objectID, &parentID); sprintf(last_artist.parentID, MUSIC_ARTIST_ID"$%llX", (long long)parentID); strncpyt(last_artist.name, artist, sizeof(last_artist.name)); last_artistAlbum.name[0] = '\0'; /* Add this file to the "- All Albums -" container as well */ insert_container(_("- All Albums -"), last_artist.parentID, NULL, "album", artist, genre, NULL, &objectID, &parentID); sprintf(last_artistAlbumAll.parentID, "%s$%llX", last_artist.parentID, (long long)parentID); last_artistAlbumAll.objectID = objectID; } else { last_artistAlbumAll.objectID++; } if( valid_cache && strcmp(album?album:_("Unknown Album"), last_artistAlbum.name) == 0 ) { last_artistAlbum.objectID++; //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID); } else { insert_container(album?album:_("Unknown Album"), last_artist.parentID, album?last_album.parentID:NULL, "album.musicAlbum", artist, genre, album_art, &objectID, &parentID); sprintf(last_artistAlbum.parentID, "%s$%llX", last_artist.parentID, (long long)parentID); last_artistAlbum.objectID = objectID; strncpyt(last_artistAlbum.name, album ? album : _("Unknown Album"), sizeof(last_artistAlbum.name)); //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID); } sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", last_artistAlbum.parentID, last_artistAlbum.objectID, last_artistAlbum.parentID, refID, class, (long long)detailID, name); sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", last_artistAlbumAll.parentID, last_artistAlbumAll.objectID, last_artistAlbumAll.parentID, refID, class, (long long)detailID, name); } if( genre ) { if( !valid_cache || strcmp(genre, last_genre.name) != 0 ) { insert_container(genre, MUSIC_GENRE_ID, NULL, "genre.musicGenre", NULL, NULL, NULL, &objectID, &parentID); sprintf(last_genre.parentID, MUSIC_GENRE_ID"$%llX", (long long)parentID); strncpyt(last_genre.name, genre, sizeof(last_genre.name)); /* Add this file to the "- All Artists -" container as well */ insert_container(_("- All Artists -"), last_genre.parentID, NULL, "person", NULL, genre, NULL, &objectID, &parentID); sprintf(last_genreArtistAll.parentID, "%s$%llX", last_genre.parentID, (long long)parentID); last_genreArtistAll.objectID = objectID; } else { last_genreArtistAll.objectID++; } if( valid_cache && strcmp(artist?artist:_("Unknown Artist"), last_genreArtist.name) == 0 ) { last_genreArtist.objectID++; } else { insert_container(artist?artist:_("Unknown Artist"), last_genre.parentID, artist?last_artist.parentID:NULL, "person.musicArtist", NULL, genre, NULL, &objectID, &parentID); sprintf(last_genreArtist.parentID, "%s$%llX", last_genre.parentID, (long long)parentID); last_genreArtist.objectID = objectID; strncpyt(last_genreArtist.name, artist ? artist : _("Unknown Artist"), sizeof(last_genreArtist.name)); //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached genre/artist item: %s/%s/%X\n", last_genreArtist.name, last_genreArtist.parentID, last_genreArtist.objectID); } sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", last_genreArtist.parentID, last_genreArtist.objectID, last_genreArtist.parentID, refID, class, (long long)detailID, name); sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", last_genreArtistAll.parentID, last_genreArtistAll.objectID, last_genreArtistAll.parentID, refID, class, (long long)detailID, name); } /* All Music */ if( !last_all_objectID ) { last_all_objectID = get_next_available_id("OBJECTS", MUSIC_ALL_ID); } sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" " ('"MUSIC_ALL_ID"$%llX', '"MUSIC_ALL_ID"', '%s', '%s', %lld, %Q)", last_all_objectID++, refID, class, (long long)detailID, name); } else if( strstr(class, "videoItem") ) { static long long last_all_objectID = 0; /* All Videos */ if( !last_all_objectID ) { last_all_objectID = get_next_available_id("OBJECTS", VIDEO_ALL_ID); } sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" " ('"VIDEO_ALL_ID"$%llX', '"VIDEO_ALL_ID"', '%s', '%s', %lld, %Q)", last_all_objectID++, refID, class, (long long)detailID, name); return; } else { return; } sqlite3_free_table(result); valid_cache = 1; } int64_t insert_directory(const char *name, const char *path, const char *base, const char *parentID, int objectID) { int64_t detailID = 0; char class[] = "container.storageFolder"; char *result, *p; static char last_found[256] = "-1"; if( strcmp(base, BROWSEDIR_ID) != 0 ) { int found = 0; char id_buf[64], parent_buf[64], refID[64]; char *dir_buf, *dir; dir_buf = strdup(path); dir = dirname(dir_buf); snprintf(refID, sizeof(refID), "%s%s$%X", BROWSEDIR_ID, parentID, objectID); snprintf(id_buf, sizeof(id_buf), "%s%s$%X", base, parentID, objectID); snprintf(parent_buf, sizeof(parent_buf), "%s%s", base, parentID); while( !found ) { if( valid_cache && strcmp(id_buf, last_found) == 0 ) break; if( sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", id_buf) > 0 ) { strcpy(last_found, id_buf); break; } /* Does not exist. Need to create, and may need to create parents also */ result = sql_get_text_field(db, "SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s'", refID); if( result ) { detailID = strtoll(result, NULL, 10); sqlite3_free(result); } sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " "VALUES" " ('%s', '%s', %Q, %lld, '%s', '%q')", id_buf, parent_buf, refID, detailID, class, strrchr(dir, '/')+1); if( (p = strrchr(id_buf, '$')) ) *p = '\0'; if( (p = strrchr(parent_buf, '$')) ) *p = '\0'; if( (p = strrchr(refID, '$')) ) *p = '\0'; dir = dirname(dir); } free(dir_buf); return 0; } detailID = GetFolderMetadata(name, path, NULL, NULL, find_album_art(path, NULL, 0)); sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME) " "VALUES" " ('%s%s$%X', '%s%s', %lld, '%s', '%q')", base, parentID, objectID, base, parentID, detailID, class, name); return detailID; } int insert_file(char *name, const char *path, const char *parentID, int object, media_types types) { char class[32]; char objectID[64]; int64_t detailID = 0; char base[8]; char *typedir_parentID; char *baseid; char *orig_name = NULL; if( (types & TYPE_IMAGES) && is_image(name) ) { if( is_album_art(name) ) return -1; strcpy(base, IMAGE_DIR_ID); strcpy(class, "item.imageItem.photo"); detailID = GetImageMetadata(path, name); } else if( (types & TYPE_VIDEO) && is_video(name) ) { orig_name = strdup(name); strcpy(base, VIDEO_DIR_ID); strcpy(class, "item.videoItem"); detailID = GetVideoMetadata(path, name); if( !detailID ) strcpy(name, orig_name); } else if( is_playlist(name) ) { if( insert_playlist(path, name) == 0 ) return 1; } if( !detailID && (types & TYPE_AUDIO) && is_audio(name) ) { strcpy(base, MUSIC_DIR_ID); strcpy(class, "item.audioItem.musicTrack"); detailID = GetAudioMetadata(path, name); } free(orig_name); if( !detailID ) { DPRINTF(E_WARN, L_SCANNER, "Unsuccessful getting details for %s!\n", path); return -1; } sprintf(objectID, "%s%s$%X", BROWSEDIR_ID, parentID, object); sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, NAME) " "VALUES" " ('%s', '%s%s', '%s', %lld, '%q')", objectID, BROWSEDIR_ID, parentID, class, detailID, name); if( *parentID ) { int typedir_objectID = 0; typedir_parentID = strdup(parentID); baseid = strrchr(typedir_parentID, '$'); if( baseid ) { typedir_objectID = strtol(baseid+1, NULL, 16); *baseid = '\0'; } insert_directory(name, path, base, typedir_parentID, typedir_objectID); free(typedir_parentID); } sql_exec(db, "INSERT into OBJECTS" " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " "VALUES" " ('%s%s$%X', '%s%s', '%s', '%s', %lld, '%q')", base, parentID, object, base, parentID, objectID, class, detailID, name); insert_containers(name, path, objectID, class, detailID); return 0; } int CreateDatabase(void) { int ret, i; const char *containers[] = { "0","-1", "root", MUSIC_ID, "0", _("Music"), MUSIC_ALL_ID, MUSIC_ID, _("All Music"), MUSIC_GENRE_ID, MUSIC_ID, _("Genre"), MUSIC_ARTIST_ID, MUSIC_ID, _("Artist"), MUSIC_ALBUM_ID, MUSIC_ID, _("Album"), MUSIC_DIR_ID, MUSIC_ID, _("Folders"), MUSIC_PLIST_ID, MUSIC_ID, _("Playlists"), VIDEO_ID, "0", _("Video"), VIDEO_ALL_ID, VIDEO_ID, _("All Video"), VIDEO_DIR_ID, VIDEO_ID, _("Folders"), IMAGE_ID, "0", _("Pictures"), IMAGE_ALL_ID, IMAGE_ID, _("All Pictures"), IMAGE_DATE_ID, IMAGE_ID, _("Date Taken"), IMAGE_CAMERA_ID, IMAGE_ID, _("Camera"), IMAGE_DIR_ID, IMAGE_ID, _("Folders"), BROWSEDIR_ID, "0", _("Browse Folders"), 0 }; ret = sql_exec(db, create_objectTable_sqlite); if( ret != SQLITE_OK ) goto sql_failed; ret = sql_exec(db, create_detailTable_sqlite); if( ret != SQLITE_OK ) goto sql_failed; ret = sql_exec(db, create_albumArtTable_sqlite); if( ret != SQLITE_OK ) goto sql_failed; ret = sql_exec(db, create_captionTable_sqlite); if( ret != SQLITE_OK ) goto sql_failed; ret = sql_exec(db, create_bookmarkTable_sqlite); if( ret != SQLITE_OK ) goto sql_failed; ret = sql_exec(db, create_playlistTable_sqlite); if( ret != SQLITE_OK ) goto sql_failed; ret = sql_exec(db, create_settingsTable_sqlite); if( ret != SQLITE_OK ) goto sql_failed; ret = sql_exec(db, "INSERT into SETTINGS values ('UPDATE_ID', '0')"); if( ret != SQLITE_OK ) goto sql_failed; for( i=0; containers[i]; i=i+3 ) { ret = sql_exec(db, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME)" " values " "('%s', '%s', %lld, 'container.storageFolder', '%q')", containers[i], containers[i+1], GetFolderMetadata(containers[i+2], NULL, NULL, NULL, 0), containers[i+2]); if( ret != SQLITE_OK ) goto sql_failed; } for( i=0; magic_containers[i].objectid_match; i++ ) { struct magic_container_s *magic = &magic_containers[i]; if (!magic->name) continue; if( sql_get_int_field(db, "SELECT 1 from OBJECTS where OBJECT_ID = '%s'", magic->objectid_match) == 0 ) { char *parent = strdup(magic->objectid_match); if (strrchr(parent, '$')) *strrchr(parent, '$') = '\0'; ret = sql_exec(db, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME)" " values " "('%s', '%s', %lld, 'container.storageFolder', '%q')", magic->objectid_match, parent, GetFolderMetadata(magic->name, NULL, NULL, NULL, 0), magic->name); free(parent); if( ret != SQLITE_OK ) goto sql_failed; } } sql_exec(db, "create INDEX IDX_OBJECTS_OBJECT_ID ON OBJECTS(OBJECT_ID);"); sql_exec(db, "create INDEX IDX_OBJECTS_PARENT_ID ON OBJECTS(PARENT_ID);"); sql_exec(db, "create INDEX IDX_OBJECTS_DETAIL_ID ON OBJECTS(DETAIL_ID);"); sql_exec(db, "create INDEX IDX_OBJECTS_CLASS ON OBJECTS(CLASS);"); sql_exec(db, "create INDEX IDX_DETAILS_PATH ON DETAILS(PATH);"); sql_exec(db, "create INDEX IDX_DETAILS_ID ON DETAILS(ID);"); sql_exec(db, "create INDEX IDX_ALBUM_ART ON ALBUM_ART(ID);"); sql_exec(db, "create INDEX IDX_SCANNER_OPT ON OBJECTS(PARENT_ID, NAME, OBJECT_ID);"); sql_failed: if( ret != SQLITE_OK ) DPRINTF(E_ERROR, L_DB_SQL, "Error creating SQLite3 database!\n"); return (ret != SQLITE_OK); } static inline int filter_hidden(scan_filter *d) { return (d->d_name[0] != '.'); } static int filter_type(scan_filter *d) { #if HAVE_STRUCT_DIRENT_D_TYPE return ( (d->d_type == DT_DIR) || (d->d_type == DT_LNK) || (d->d_type == DT_UNKNOWN) ); #else return 1; #endif } static int filter_a(scan_filter *d) { return ( filter_hidden(d) && (filter_type(d) || (is_reg(d) && (is_audio(d->d_name) || is_playlist(d->d_name)))) ); } static int filter_av(scan_filter *d) { return ( filter_hidden(d) && (filter_type(d) || (is_reg(d) && (is_audio(d->d_name) || is_video(d->d_name) || is_playlist(d->d_name)))) ); } static int filter_ap(scan_filter *d) { return ( filter_hidden(d) && (filter_type(d) || (is_reg(d) && (is_audio(d->d_name) || is_image(d->d_name) || is_playlist(d->d_name)))) ); } static int filter_v(scan_filter *d) { return ( filter_hidden(d) && (filter_type(d) || (is_reg(d) && is_video(d->d_name))) ); } static int filter_vp(scan_filter *d) { return ( filter_hidden(d) && (filter_type(d) || (is_reg(d) && (is_video(d->d_name) || is_image(d->d_name)))) ); } static int filter_p(scan_filter *d) { return ( filter_hidden(d) && (filter_type(d) || (is_reg(d) && is_image(d->d_name))) ); } static int filter_avp(scan_filter *d) { return ( filter_hidden(d) && (filter_type(d) || (is_reg(d) && (is_audio(d->d_name) || is_image(d->d_name) || is_video(d->d_name) || is_playlist(d->d_name)))) ); } static void ScanDirectory(const char *dir, const char *parent, media_types dir_types) { struct dirent **namelist; int i, n, startID = 0; char *full_path; char *name = NULL; static long long unsigned int fileno = 0; enum file_types type; DPRINTF(parent?E_INFO:E_WARN, L_SCANNER, _("Scanning %s\n"), dir); switch( dir_types ) { case ALL_MEDIA: n = scandir(dir, &namelist, filter_avp, alphasort); break; case TYPE_AUDIO: n = scandir(dir, &namelist, filter_a, alphasort); break; case TYPE_AUDIO|TYPE_VIDEO: n = scandir(dir, &namelist, filter_av, alphasort); break; case TYPE_AUDIO|TYPE_IMAGES: n = scandir(dir, &namelist, filter_ap, alphasort); break; case TYPE_VIDEO: n = scandir(dir, &namelist, filter_v, alphasort); break; case TYPE_VIDEO|TYPE_IMAGES: n = scandir(dir, &namelist, filter_vp, alphasort); break; case TYPE_IMAGES: n = scandir(dir, &namelist, filter_p, alphasort); break; default: n = -1; errno = EINVAL; break; } if( n < 0 ) { DPRINTF(E_WARN, L_SCANNER, "Error scanning %s [%s]\n", dir, strerror(errno)); return; } full_path = malloc(PATH_MAX); if (!full_path) { DPRINTF(E_ERROR, L_SCANNER, "Memory allocation failed scanning %s\n", dir); return; } if( !parent ) { startID = get_next_available_id("OBJECTS", BROWSEDIR_ID); } for (i=0; i < n; i++) { #if !USE_FORK if( quitting ) break; #endif type = TYPE_UNKNOWN; snprintf(full_path, PATH_MAX, "%s/%s", dir, namelist[i]->d_name); name = escape_tag(namelist[i]->d_name, 1); if( is_dir(namelist[i]) == 1 ) { type = TYPE_DIR; } else if( is_reg(namelist[i]) == 1 ) { type = TYPE_FILE; } else { type = resolve_unknown_type(full_path, dir_types); } if( (type == TYPE_DIR) && (access(full_path, R_OK|X_OK) == 0) ) { char *parent_id; insert_directory(name, full_path, BROWSEDIR_ID, THISORNUL(parent), i+startID); xasprintf(&parent_id, "%s$%X", THISORNUL(parent), i+startID); ScanDirectory(full_path, parent_id, dir_types); free(parent_id); } else if( type == TYPE_FILE && (access(full_path, R_OK) == 0) ) { if( insert_file(name, full_path, THISORNUL(parent), i+startID, dir_types) == 0 ) fileno++; } free(name); free(namelist[i]); } free(namelist); free(full_path); if( !parent ) { DPRINTF(E_WARN, L_SCANNER, _("Scanning %s finished (%llu files)!\n"), dir, fileno); } } static void _notify_start(void) { #ifdef READYNAS FILE *flag = fopen("/ramfs/.upnp-av_scan", "w"); if( flag ) fclose(flag); #endif } static void _notify_stop(void) { #ifdef READYNAS if( access("/ramfs/.rescan_done", F_OK) == 0 ) system("/bin/sh /ramfs/.rescan_done"); unlink("/ramfs/.upnp-av_scan"); #endif } void start_scanner() { struct media_dir_s *media_path; char path[MAXPATHLEN]; if (setpriority(PRIO_PROCESS, 0, 15) == -1) DPRINTF(E_WARN, L_INOTIFY, "Failed to reduce scanner thread priority\n"); _notify_start(); setlocale(LC_COLLATE, ""); av_register_all(); av_log_set_level(AV_LOG_PANIC); for( media_path = media_dirs; media_path != NULL; media_path = media_path->next ) { int64_t id; char *bname, *parent = NULL; char buf[8]; strncpyt(path, media_path->path, sizeof(path)); bname = basename(path); /* If there are multiple media locations, add a level to the ContentDirectory */ if( !GETFLAG(MERGE_MEDIA_DIRS_MASK) && media_dirs->next ) { int startID = get_next_available_id("OBJECTS", BROWSEDIR_ID); id = insert_directory(bname, path, BROWSEDIR_ID, "", startID); sprintf(buf, "$%X", startID); parent = buf; } else id = GetFolderMetadata(bname, media_path->path, NULL, NULL, 0); /* Use TIMESTAMP to store the media type */ sql_exec(db, "UPDATE DETAILS set TIMESTAMP = %d where ID = %lld", media_path->types, (long long)id); ScanDirectory(media_path->path, parent, media_path->types); sql_exec(db, "INSERT into SETTINGS values (%Q, %Q)", "media_dir", media_path->path); } _notify_stop(); /* Create this index after scanning, so it doesn't slow down the scanning process. * This index is very useful for large libraries used with an XBox360 (or any * client that uses UPnPSearch on large containers). */ sql_exec(db, "create INDEX IDX_SEARCH_OPT ON OBJECTS(OBJECT_ID, CLASS, DETAIL_ID);"); if( GETFLAG(NO_PLAYLIST_MASK) ) { DPRINTF(E_WARN, L_SCANNER, "Playlist creation disabled\n"); } else { fill_playlists(); } DPRINTF(E_DEBUG, L_SCANNER, "Initial file scan completed\n"); //JM: Set up a db version number, so we know if we need to rebuild due to a new structure. sql_exec(db, "pragma user_version = %d;", DB_VERSION); } minidlna-1.1.5+dfsg/scanner.h000066400000000000000000000043441261774340000160430ustar00rootroot00000000000000/* Media file scanner * * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Justin Maggard * * MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #ifndef __SCANNER_H__ #define __SCANNER_H__ /* Try to be generally PlaysForSure compatible by using similar IDs */ #define BROWSEDIR_ID "64" #define MUSIC_ID "1" #define MUSIC_ALL_ID "1$4" #define MUSIC_GENRE_ID "1$5" #define MUSIC_ARTIST_ID "1$6" #define MUSIC_ALBUM_ID "1$7" #define MUSIC_PLIST_ID "1$F" #define MUSIC_DIR_ID "1$14" #define MUSIC_CONTRIB_ARTIST_ID "1$100" #define MUSIC_ALBUM_ARTIST_ID "1$107" #define MUSIC_COMPOSER_ID "1$108" #define MUSIC_RATING_ID "1$101" #define VIDEO_ID "2" #define VIDEO_ALL_ID "2$8" #define VIDEO_GENRE_ID "2$9" #define VIDEO_ACTOR_ID "2$A" #define VIDEO_SERIES_ID "2$E" #define VIDEO_PLIST_ID "2$10" #define VIDEO_DIR_ID "2$15" #define VIDEO_RATING_ID "2$200" #define IMAGE_ID "3" #define IMAGE_ALL_ID "3$B" #define IMAGE_DATE_ID "3$C" #define IMAGE_ALBUM_ID "3$D" #define IMAGE_CAMERA_ID "3$D2" // PlaysForSure == Keyword #define IMAGE_PLIST_ID "3$11" #define IMAGE_DIR_ID "3$16" #define IMAGE_RATING_ID "3$300" extern int valid_cache; int is_video(const char *file); int is_audio(const char *file); int is_image(const char *file); int64_t get_next_available_id(const char *table, const char *parentID); int64_t insert_directory(const char *name, const char *path, const char *base, const char *parentID, int objectID); int insert_file(char *name, const char *path, const char *parentID, int object, media_types dir_types); int CreateDatabase(void); void start_scanner(); #endif minidlna-1.1.5+dfsg/scanner_sqlite.h000066400000000000000000000053001261774340000174150ustar00rootroot00000000000000/* Media table definitions for SQLite database * * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Douglas Carmichael * * MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ char create_objectTable_sqlite[] = "CREATE TABLE OBJECTS (" "ID INTEGER PRIMARY KEY AUTOINCREMENT, " "OBJECT_ID TEXT UNIQUE NOT NULL, " "PARENT_ID TEXT NOT NULL, " "REF_ID TEXT DEFAULT NULL, " "CLASS TEXT NOT NULL, " "DETAIL_ID INTEGER DEFAULT NULL, " "NAME TEXT DEFAULT NULL);"; char create_detailTable_sqlite[] = "CREATE TABLE DETAILS (" "ID INTEGER PRIMARY KEY AUTOINCREMENT, " "PATH TEXT DEFAULT NULL, " "SIZE INTEGER, " "TIMESTAMP INTEGER, " "TITLE TEXT COLLATE NOCASE, " "DURATION TEXT, " "BITRATE INTEGER, " "SAMPLERATE INTEGER, " "CREATOR TEXT COLLATE NOCASE, " "ARTIST TEXT COLLATE NOCASE, " "ALBUM TEXT COLLATE NOCASE, " "GENRE TEXT COLLATE NOCASE, " "COMMENT TEXT, " "CHANNELS INTEGER, " "DISC INTEGER, " "TRACK INTEGER, " "DATE DATE, " "RESOLUTION TEXT, " "THUMBNAIL BOOL DEFAULT 0, " "ALBUM_ART INTEGER DEFAULT 0, " "ROTATION INTEGER, " "DLNA_PN TEXT, " "MIME TEXT);"; char create_albumArtTable_sqlite[] = "CREATE TABLE ALBUM_ART (" "ID INTEGER PRIMARY KEY AUTOINCREMENT, " "PATH TEXT NOT NULL" ");"; char create_captionTable_sqlite[] = "CREATE TABLE CAPTIONS (" "ID INTEGER PRIMARY KEY, " "PATH TEXT NOT NULL" ");"; char create_bookmarkTable_sqlite[] = "CREATE TABLE BOOKMARKS (" "ID INTEGER PRIMARY KEY, " "SEC INTEGER" ");"; char create_playlistTable_sqlite[] = "CREATE TABLE PLAYLISTS (" "ID INTEGER PRIMARY KEY AUTOINCREMENT, " "NAME TEXT NOT NULL, " "PATH TEXT NOT NULL, " "ITEMS INTEGER DEFAULT 0, " "FOUND INTEGER DEFAULT 0" ");"; char create_settingsTable_sqlite[] = "CREATE TABLE SETTINGS (" "KEY TEXT NOT NULL, " "VALUE TEXT" ");"; minidlna-1.1.5+dfsg/sendfile.h000066400000000000000000000030471261774340000162020ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2013 NETGEAR * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #if defined(HAVE_LINUX_SENDFILE_API) #include int sys_sendfile(int sock, int sendfd, off_t *offset, off_t len) { return sendfile(sock, sendfd, offset, len); } #elif defined(HAVE_DARWIN_SENDFILE_API) #include #include #include int sys_sendfile(int sock, int sendfd, off_t *offset, off_t len) { int ret; ret = sendfile(sendfd, sock, *offset, &len, NULL, 0); *offset += len; return ret; } #elif defined(HAVE_FREEBSD_SENDFILE_API) #include #include #include int sys_sendfile(int sock, int sendfd, off_t *offset, off_t len) { int ret; size_t nbytes = len; ret = sendfile(sendfd, sock, *offset, nbytes, NULL, &len, SF_MNOWAIT); *offset += len; return ret; } #else #include int sys_sendfile(int sock, int sendfd, off_t *offset, off_t len) { errno = EINVAL; return -1; } #endif minidlna-1.1.5+dfsg/sql.c000066400000000000000000000133711261774340000152040ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include #include #include #include "sql.h" #include "upnpglobalvars.h" #include "log.h" int sql_exec(sqlite3 *db, const char *fmt, ...) { int ret; char *errMsg = NULL; char *sql; va_list ap; //DPRINTF(E_DEBUG, L_DB_SQL, "SQL: %s\n", sql); va_start(ap, fmt); sql = sqlite3_vmprintf(fmt, ap); va_end(ap); ret = sqlite3_exec(db, sql, 0, 0, &errMsg); if( ret != SQLITE_OK ) { DPRINTF(E_ERROR, L_DB_SQL, "SQL ERROR %d [%s]\n%s\n", ret, errMsg, sql); if (errMsg) sqlite3_free(errMsg); } sqlite3_free(sql); return ret; } int sql_get_table(sqlite3 *db, const char *sql, char ***pazResult, int *pnRow, int *pnColumn) { int ret; char *errMsg = NULL; //DPRINTF(E_DEBUG, L_DB_SQL, "SQL: %s\n", sql); ret = sqlite3_get_table(db, sql, pazResult, pnRow, pnColumn, &errMsg); if( ret != SQLITE_OK ) { DPRINTF(E_ERROR, L_DB_SQL, "SQL ERROR %d [%s]\n%s\n", ret, errMsg, sql); if (errMsg) sqlite3_free(errMsg); } return ret; } int sql_get_int_field(sqlite3 *db, const char *fmt, ...) { va_list ap; int counter, result; char *sql; int ret; sqlite3_stmt *stmt; va_start(ap, fmt); sql = sqlite3_vmprintf(fmt, ap); va_end(ap); //DPRINTF(E_DEBUG, L_DB_SQL, "sql: %s\n", sql); switch (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL)) { case SQLITE_OK: break; default: DPRINTF(E_ERROR, L_DB_SQL, "prepare failed: %s\n%s\n", sqlite3_errmsg(db), sql); sqlite3_free(sql); return -1; } for (counter = 0; ((result = sqlite3_step(stmt)) == SQLITE_BUSY || result == SQLITE_LOCKED) && counter < 2; counter++) { /* While SQLITE_BUSY has a built in timeout, SQLITE_LOCKED does not, so sleep */ if (result == SQLITE_LOCKED) sleep(1); } switch (result) { case SQLITE_DONE: /* no rows returned */ ret = 0; break; case SQLITE_ROW: if (sqlite3_column_type(stmt, 0) == SQLITE_NULL) { ret = 0; break; } ret = sqlite3_column_int(stmt, 0); break; default: DPRINTF(E_WARN, L_DB_SQL, "%s: step failed: %s\n%s\n", __func__, sqlite3_errmsg(db), sql); ret = -1; break; } sqlite3_free(sql); sqlite3_finalize(stmt); return ret; } int64_t sql_get_int64_field(sqlite3 *db, const char *fmt, ...) { va_list ap; int counter, result; char *sql; int64_t ret; sqlite3_stmt *stmt; va_start(ap, fmt); sql = sqlite3_vmprintf(fmt, ap); va_end(ap); //DPRINTF(E_DEBUG, L_DB_SQL, "sql: %s\n", sql); switch (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL)) { case SQLITE_OK: break; default: DPRINTF(E_ERROR, L_DB_SQL, "prepare failed: %s\n%s\n", sqlite3_errmsg(db), sql); sqlite3_free(sql); return -1; } for (counter = 0; ((result = sqlite3_step(stmt)) == SQLITE_BUSY || result == SQLITE_LOCKED) && counter < 2; counter++) { /* While SQLITE_BUSY has a built in timeout, SQLITE_LOCKED does not, so sleep */ if (result == SQLITE_LOCKED) sleep(1); } switch (result) { case SQLITE_DONE: /* no rows returned */ ret = 0; break; case SQLITE_ROW: if (sqlite3_column_type(stmt, 0) == SQLITE_NULL) { ret = 0; break; } ret = sqlite3_column_int64(stmt, 0); break; default: DPRINTF(E_WARN, L_DB_SQL, "%s: step failed: %s\n%s\n", __func__, sqlite3_errmsg(db), sql); ret = -1; break; } sqlite3_free(sql); sqlite3_finalize(stmt); return ret; } char * sql_get_text_field(sqlite3 *db, const char *fmt, ...) { va_list ap; int counter, result, len; char *sql; char *str; sqlite3_stmt *stmt; if (db == NULL) { DPRINTF(E_WARN, L_DB_SQL, "db is NULL\n"); return NULL; } va_start(ap, fmt); sql = sqlite3_vmprintf(fmt, ap); va_end(ap); //DPRINTF(E_DEBUG, L_DB_SQL, "sql: %s\n", sql); switch (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL)) { case SQLITE_OK: break; default: DPRINTF(E_ERROR, L_DB_SQL, "prepare failed: %s\n%s\n", sqlite3_errmsg(db), sql); sqlite3_free(sql); return NULL; } sqlite3_free(sql); for (counter = 0; ((result = sqlite3_step(stmt)) == SQLITE_BUSY || result == SQLITE_LOCKED) && counter < 2; counter++) { /* While SQLITE_BUSY has a built in timeout, * SQLITE_LOCKED does not, so sleep */ if (result == SQLITE_LOCKED) sleep(1); } switch (result) { case SQLITE_DONE: /* no rows returned */ str = NULL; break; case SQLITE_ROW: if (sqlite3_column_type(stmt, 0) == SQLITE_NULL) { str = NULL; break; } len = sqlite3_column_bytes(stmt, 0); if ((str = sqlite3_malloc(len + 1)) == NULL) { DPRINTF(E_ERROR, L_DB_SQL, "malloc failed\n"); break; } strncpy(str, (char *)sqlite3_column_text(stmt, 0), len + 1); break; default: DPRINTF(E_WARN, L_DB_SQL, "SQL step failed: %s\n", sqlite3_errmsg(db)); str = NULL; break; } sqlite3_finalize(stmt); return str; } int db_upgrade(sqlite3 *db) { int db_vers; db_vers = sql_get_int_field(db, "PRAGMA user_version"); if (db_vers == DB_VERSION) return 0; if (db_vers > DB_VERSION) return -2; if (db_vers < 1) return -1; if (db_vers < 9) return db_vers; sql_exec(db, "PRAGMA user_version = %d", DB_VERSION); return 0; } minidlna-1.1.5+dfsg/sql.h000066400000000000000000000026561261774340000152150ustar00rootroot00000000000000/* Reusable SQLite3 wrapper functions * * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Justin Maggard * * MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #ifndef __SQL_H__ #define __SQL_H__ #include #include #ifndef HAVE_SQLITE3_MALLOC #define sqlite3_malloc(size) sqlite3_mprintf("%*s", size, "") #endif #ifndef HAVE_SQLITE3_PREPARE_V2 #define sqlite3_prepare_v2 sqlite3_prepare #endif int sql_exec(sqlite3 *db, const char *fmt, ...); int sql_get_table(sqlite3 *db, const char *zSql, char ***pazResult, int *pnRow, int *pnColumn); int sql_get_int_field(sqlite3 *db, const char *fmt, ...); int64_t sql_get_int64_field(sqlite3 *db, const char *fmt, ...); char * sql_get_text_field(sqlite3 *db, const char *fmt, ...); int db_upgrade(sqlite3 *db); #endif minidlna-1.1.5+dfsg/tagutils/000077500000000000000000000000001261774340000160705ustar00rootroot00000000000000minidlna-1.1.5+dfsg/tagutils/tagutils-aac.c000066400000000000000000000263651261774340000206260ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-aac.c // DESCRIPTION : AAC metadata reader //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* * 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, see . */ /* * This file is derived from mt-daap project. */ // _aac_findatom: static long _aac_findatom(FILE *fin, long max_offset, char *which_atom, int *atom_size) { long current_offset = 0; int size; char atom[4]; while(current_offset < max_offset) { if(fread((void*)&size, 1, sizeof(int), fin) != sizeof(int)) return -1; size = ntohl(size); if(size <= 7) return -1; if(fread(atom, 1, 4, fin) != 4) return -1; if(strncasecmp(atom, which_atom, 4) == 0) { *atom_size = size; return current_offset; } fseek(fin, size - 8, SEEK_CUR); current_offset += size; } return -1; } // _get_aactags static int _get_aactags(char *file, struct song_metadata *psong) { FILE *fin; long atom_offset; unsigned int atom_length; long current_offset = 0; int current_size; char current_atom[4]; char *current_data = NULL; int genre; int len; if(!(fin = fopen(file, "rb"))) { DPRINTF(E_ERROR, L_SCANNER, "Cannot open file %s for reading\n", file); return -1; } fseek(fin, 0, SEEK_SET); atom_offset = _aac_lookforatom(fin, "moov:udta:meta:ilst", &atom_length); if(atom_offset != -1) { while(current_offset < atom_length) { if(fread((void*)¤t_size, 1, sizeof(int), fin) != sizeof(int)) break; current_size = ntohl(current_size); if(current_size <= 7 || current_size > 1<<24) // something not right break; if(fread(current_atom, 1, 4, fin) != 4) break; len = current_size - 7; // too short if(len < 22) len = 22; current_data = (char*)malloc(len); // extra byte if(fread(current_data, 1, current_size - 8, fin) != current_size - 8) break; current_data[current_size - 8] = '\0'; if(!memcmp(current_atom, "\xA9" "nam", 4)) psong->title = strdup((char*)¤t_data[16]); else if(!memcmp(current_atom, "\xA9" "ART", 4) || !memcmp(current_atom, "\xA9" "art", 4)) psong->contributor[ROLE_ARTIST] = strdup((char*)¤t_data[16]); else if(!memcmp(current_atom, "\xA9" "alb", 4)) psong->album = strdup((char*)¤t_data[16]); else if(!memcmp(current_atom, "\xA9" "cmt", 4)) psong->comment = strdup((char*)¤t_data[16]); else if(!memcmp(current_atom, "aART", 4) || !memcmp(current_atom, "aart", 4)) psong->contributor[ROLE_ALBUMARTIST] = strdup((char*)¤t_data[16]); else if(!memcmp(current_atom, "\xA9" "dir", 4)) psong->contributor[ROLE_CONDUCTOR] = strdup((char*)¤t_data[16]); else if(!memcmp(current_atom, "\xA9" "wrt", 4)) psong->contributor[ROLE_COMPOSER] = strdup((char*)¤t_data[16]); else if(!memcmp(current_atom, "\xA9" "grp", 4)) psong->grouping = strdup((char*)¤t_data[16]); else if(!memcmp(current_atom, "\xA9" "gen", 4)) psong->genre = strdup((char*)¤t_data[16]); else if(!memcmp(current_atom, "\xA9" "day", 4)) psong->year = atoi((char*)¤t_data[16]); else if(!memcmp(current_atom, "tmpo", 4)) psong->bpm = (current_data[16] << 8) | current_data[17]; else if(!memcmp(current_atom, "trkn", 4)) { psong->track = (current_data[18] << 8) | current_data[19]; psong->total_tracks = (current_data[20] << 8) | current_data[21]; } else if(!memcmp(current_atom, "disk", 4)) { psong->disc = (current_data[18] << 8) | current_data[19]; psong->total_discs = (current_data[20] << 8) | current_data[21]; } else if(!memcmp(current_atom, "gnre", 4)) { genre = current_data[17] - 1; if((genre < 0) || (genre > WINAMP_GENRE_UNKNOWN)) genre = WINAMP_GENRE_UNKNOWN; psong->genre = strdup(winamp_genre[genre]); } else if(!memcmp(current_atom, "cpil", 4)) { psong->compilation = current_data[16]; } else if(!memcmp(current_atom, "covr", 4)) { psong->image_size = current_size - 8 - 16; if((psong->image = malloc(psong->image_size))) memcpy(psong->image, current_data+16, psong->image_size); else DPRINTF(E_ERROR, L_SCANNER, "Out of memory [%s]\n", file); } free(current_data); current_data = NULL; current_offset += current_size; } } fclose(fin); free(current_data); if(atom_offset == -1) return -1; return 0; } // aac_lookforatom static off_t _aac_lookforatom(FILE *aac_fp, char *atom_path, unsigned int *atom_length) { long atom_offset; off_t file_size; char *cur_p, *end_p; char atom_name[5]; fseek(aac_fp, 0, SEEK_END); file_size = ftell(aac_fp); rewind(aac_fp); end_p = atom_path; while(*end_p != '\0') { end_p++; } atom_name[4] = '\0'; cur_p = atom_path; while(cur_p) { if((end_p - cur_p) < 4) { return -1; } strncpy(atom_name, cur_p, 4); atom_offset = _aac_findatom(aac_fp, file_size, atom_name, (int*)atom_length); if(atom_offset == -1) { return -1; } cur_p = strchr(cur_p, ':'); if(cur_p != NULL) { cur_p++; if(!strcmp(atom_name, "meta")) { fseek(aac_fp, 4, SEEK_CUR); } else if(!strcmp(atom_name, "stsd")) { fseek(aac_fp, 8, SEEK_CUR); } else if(!strcmp(atom_name, "mp4a")) { fseek(aac_fp, 28, SEEK_CUR); } } } // return position of 'size:atom' return ftell(aac_fp) - 8; } int _aac_check_extended_descriptor(FILE *infile) { short int i; unsigned char buf[3]; if( fread((void *)&buf, 1, 3, infile) < 3 ) return -1; for( i=0; i<3; i++ ) { if( (buf[i] != 0x80) && (buf[i] != 0x81) && (buf[i] != 0xFE) ) { fseek(infile, -3, SEEK_CUR); return 0; } } return 0; } // _get_aacfileinfo int _get_aacfileinfo(char *file, struct song_metadata *psong) { FILE *infile; long atom_offset; int atom_length; int sample_size; int samples; unsigned int bitrate; off_t file_size; int ms; unsigned char buffer[2]; aac_object_type_t profile_id = 0; psong->vbr_scale = -1; psong->channels = 2; // A "normal" default in case we can't find this information infile = fopen(file, "rb"); if(!infile) { DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file); return -1; } fseek(infile, 0, SEEK_END); file_size = ftell(infile); fseek(infile, 0, SEEK_SET); // move to 'mvhd' atom atom_offset = _aac_lookforatom(infile, "moov:mvhd", (unsigned int*)&atom_length); if(atom_offset != -1) { fseek(infile, 12, SEEK_CUR); if(fread((void*)&sample_size, 1, sizeof(int), infile) != sizeof(int) || fread((void*)&samples, 1, sizeof(int), infile) != sizeof(int)) { fclose(infile); return -1; } sample_size = ntohl(sample_size); samples = ntohl(samples); // avoid overflowing on large sample_sizes (90000) ms = 1000; while((ms > 9) && (!(sample_size % 10))) { sample_size /= 10; ms /= 10; } // unit = ms psong->song_length = (int)((samples * ms) / sample_size); } psong->bitrate = 0; // see if it is aac or alac atom_offset = _aac_lookforatom(infile, "moov:trak:mdia:minf:stbl:stsd:alac", (unsigned int*)&atom_length); if(atom_offset != -1) { fseek(infile, atom_offset + 32, SEEK_SET); if (fread(buffer, sizeof(unsigned char), 2, infile) == 2) psong->samplerate = (buffer[0] << 8) | (buffer[1]); goto bad_esds; } // get samplerate from 'mp4a' (not from 'mdhd') atom_offset = _aac_lookforatom(infile, "moov:trak:mdia:minf:stbl:stsd:mp4a", (unsigned int*)&atom_length); if(atom_offset != -1) { fseek(infile, atom_offset + 32, SEEK_SET); if(fread(buffer, sizeof(unsigned char), 2, infile) == 2) psong->samplerate = (buffer[0] << 8) | (buffer[1]); fseek(infile, 2, SEEK_CUR); // get bitrate from 'esds' atom_offset = _aac_findatom(infile, atom_length - (ftell(infile) - atom_offset), "esds", &atom_length); if(atom_offset != -1) { // skip the version number fseek(infile, atom_offset + 4, SEEK_CUR); // should be 0x03, to signify the descriptor type (section) if( !fread((void *)&buffer, 1, 1, infile) || (buffer[0] != 0x03) || (_aac_check_extended_descriptor(infile) != 0) ) goto bad_esds; fseek(infile, 4, SEEK_CUR); if( !fread((void *)&buffer, 1, 1, infile) || (buffer[0] != 0x04) || (_aac_check_extended_descriptor(infile) != 0) ) goto bad_esds; fseek(infile, 10, SEEK_CUR); // 10 bytes into section 4 should be average bitrate. max bitrate is 6 bytes in. if(fread((void *)&bitrate, sizeof(unsigned int), 1, infile)) psong->bitrate = ntohl(bitrate); if( !fread((void *)&buffer, 1, 1, infile) || (buffer[0] != 0x05) || (_aac_check_extended_descriptor(infile) != 0) ) goto bad_esds; fseek(infile, 1, SEEK_CUR); // 1 bytes into section 5 should be the setup data if(fread((void *)&buffer, 2, 1, infile)) { profile_id = (buffer[0] >> 3); // first 5 bits of setup data is the Audo Profile ID /* Frequency index: (((buffer[0] & 0x7) << 1) | (buffer[1] >> 7))) */ samples = ((buffer[1] >> 3) & 0xF); psong->channels = (samples == 7 ? 8 : samples); } } } bad_esds: atom_offset = _aac_lookforatom(infile, "mdat", (unsigned int*)&atom_length); psong->audio_size = atom_length - 8; psong->audio_offset = atom_offset; if(!psong->bitrate) { /* Dont' scare people with this for now. Could be Apple Lossless? DPRINTF(E_DEBUG, L_SCANNER, "No 'esds' atom. Guess bitrate. [%s]\n", basename(file)); */ if((atom_offset != -1) && (psong->song_length)) { psong->bitrate = atom_length * 1000 / psong->song_length / 128; } /* If this is an obviously wrong bitrate, try something different */ if((psong->bitrate < 16000) && (psong->song_length > 1000)) { psong->bitrate = (file_size * 8) / (psong->song_length / 1000); } } //DPRINTF(E_DEBUG, L_METADATA, "Profile ID: %u\n", profile_id); switch( profile_id ) { case AAC_LC: case AAC_LC_ER: if( psong->samplerate < 8000 || psong->samplerate > 48000 ) { DPRINTF(E_DEBUG, L_METADATA, "Unsupported AAC: sample rate is not 8000 < %d < 48000\n", psong->samplerate); break; } /* AAC @ Level 1/2 */ if( psong->channels <= 2 && psong->bitrate <= 320000 ) xasprintf(&(psong->dlna_pn), "AAC_ISO_320"); else if( psong->channels <= 2 && psong->bitrate <= 576000 ) xasprintf(&(psong->dlna_pn), "AAC_ISO"); else if( psong->channels <= 6 && psong->bitrate <= 1440000 ) xasprintf(&(psong->dlna_pn), "AAC_MULT5_ISO"); else DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC: %d channels, %d bitrate\n", psong->channels, psong->bitrate); break; default: DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC type %d [%s]\n", profile_id, basename(file)); break; } fclose(infile); return 0; } minidlna-1.1.5+dfsg/tagutils/tagutils-aac.h000066400000000000000000000023171261774340000206220ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-aac.h // DESCRIPTION : AAC metadata reader //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* * 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, see . */ static int _get_aactags(char *file, struct song_metadata *psong); static int _get_aacfileinfo(char *file, struct song_metadata *psong); static off_t _aac_lookforatom(FILE *aac_fp, char *atom_path, unsigned int *atom_length); minidlna-1.1.5+dfsg/tagutils/tagutils-asf.c000066400000000000000000000367311261774340000206510ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-asf.c // DESCRIPTION : ASF (wma/wmv) metadata reader //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* * 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, see . */ #ifdef HAVE_MACHINE_ENDIAN_H #include #else #include #endif static inline uint16_t le16_to_cpu(uint16_t le16) { #if __BYTE_ORDER == __LITTLE_ENDIAN return le16; #else uint16_t be16 = ((le16 << 8) & 0xff00) | ((le16 >> 8) & 0x00ff); return be16; #endif } static inline uint32_t le32_to_cpu(uint32_t le32) { #if __BYTE_ORDER == __LITTLE_ENDIAN return le32; #else uint32_t be32 = ((le32 << 24) & 0xff000000) | ((le32 << 8) & 0x00ff0000) | ((le32 >> 8) & 0x0000ff00) | ((le32 >> 24) & 0x000000ff); return be32; #endif } static inline uint64_t le64_to_cpu(uint64_t le64) { #if __BYTE_ORDER == __LITTLE_ENDIAN return le64; #else uint64_t be64; uint8_t *le64p = (uint8_t*)&le64; uint8_t *be64p = (uint8_t*)&be64; be64p[0] = le64p[7]; be64p[1] = le64p[6]; be64p[2] = le64p[5]; be64p[3] = le64p[4]; be64p[4] = le64p[3]; be64p[5] = le64p[2]; be64p[6] = le64p[1]; be64p[7] = le64p[0]; return be64; #endif } static inline uint32_t cpu_to_be32(uint32_t cpu32) { #if __BYTE_ORDER == __LITTLE_ENDIAN uint32_t be32 = ((cpu32 << 24) & 0xff000000) | ((cpu32 << 8) & 0x00ff0000) | ((cpu32 >> 8) & 0x0000ff00) | ((cpu32 >> 24) & 0x000000ff); return be32; #else return cpu32; #endif } static inline uint8_t fget_byte(FILE *fp) { uint8_t d; if (fread(&d, sizeof(d), 1, fp) != 1) return 0; return d; } static inline uint16_t fget_le16(FILE *fp) { uint16_t d; if (fread(&d, sizeof(d), 1, fp) != 1) return 0; d = le16_to_cpu(d); return d; } static inline uint32_t fget_le32(FILE *fp) { uint32_t d; if (fread(&d, sizeof(d), 1, fp) != 1) return 0; d = le32_to_cpu(d); return d; } // NOTE: support U+0000 ~ U+FFFF only. static int utf16le_to_utf8(char *dst, int n, uint16_t utf16le) { uint16_t wc = le16_to_cpu(utf16le); if (wc < 0x80) { if (n < 1) return 0; *dst++ = wc & 0xff; return 1; } else if (wc < 0x800) { if (n < 2) return 0; *dst++ = 0xc0 | (wc>>6); *dst++ = 0x80 | (wc & 0x3f); return 2; } else { if (n < 3) return 0; *dst++ = 0xe0 | (wc>>12); *dst++ = 0x80 | ((wc>>6) & 0x3f); *dst++ = 0x80 | (wc & 0x3f); return 3; } } static int _asf_read_file_properties(FILE *fp, asf_file_properties_t *p, uint32_t size) { int len; len = sizeof(*p) - offsetof(asf_file_properties_t, FileID); if(size < len) return -1; memset(p, 0, sizeof(*p)); p->ID = ASF_FileProperties; p->Size = size; if(len != fread(&p->FileID, 1, len, fp)) return -1; return 0; } static void _pick_dlna_profile(struct song_metadata *psong, uint16_t format) { /* DLNA Profile Name */ switch( le16_to_cpu(format) ) { case WMA: if( psong->max_bitrate < 193000 ) xasprintf(&(psong->dlna_pn), "WMABASE"); else if( psong->max_bitrate < 385000 ) xasprintf(&(psong->dlna_pn), "WMAFULL"); break; case WMAPRO: xasprintf(&(psong->dlna_pn), "WMAPRO"); break; case WMALSL: xasprintf(&(psong->dlna_pn), "WMALSL%s", psong->channels > 2 ? "_MULT5" : ""); default: break; } } static int _asf_read_audio_stream(FILE *fp, struct song_metadata *psong, int size) { asf_audio_stream_t s; int len; len = sizeof(s) - sizeof(s.Hdr); if(len > size) len = size; if(len != fread(&s.wfx, 1, len, fp)) return -1; psong->channels = le16_to_cpu(s.wfx.nChannels); psong->bitrate = le32_to_cpu(s.wfx.nAvgBytesPerSec) * 8; psong->samplerate = le32_to_cpu(s.wfx.nSamplesPerSec); if (!psong->max_bitrate) psong->max_bitrate = psong->bitrate; _pick_dlna_profile(psong, s.wfx.wFormatTag); return 0; } static int _asf_read_media_stream(FILE *fp, struct song_metadata *psong, uint32_t size) { asf_media_stream_t s; avi_audio_format_t wfx; int len; len = sizeof(s) - sizeof(s.Hdr); if(len > size) len = size; memset(&s, 0, sizeof(s)); if(len != fread(&s.MajorType, 1, len, fp)) return -1; if(IsEqualGUID(&s.MajorType, &ASF_MediaTypeAudio) && IsEqualGUID(&s.FormatType, &ASF_FormatTypeWave) && s.FormatSize >= sizeof(wfx)) { if(sizeof(wfx) != fread(&wfx, 1, sizeof(wfx), fp)) return -1; psong->channels = le16_to_cpu(wfx.nChannels); psong->bitrate = le32_to_cpu(wfx.nAvgBytesPerSec) * 8; psong->samplerate = le32_to_cpu(wfx.nSamplesPerSec); if (!psong->max_bitrate) psong->max_bitrate = psong->bitrate; _pick_dlna_profile(psong, wfx.wFormatTag); } return 0; } static int _asf_read_stream_object(FILE *fp, struct song_metadata *psong, uint32_t size) { asf_stream_object_t s; int len; len = sizeof(s) - sizeof(asf_object_t); if(size < len) return -1; memset(&s, 0, sizeof(s)); if(len != fread(&s.StreamType, 1, len, fp)) return -1; if(IsEqualGUID(&s.StreamType, &ASF_AudioStream)) _asf_read_audio_stream(fp, psong, s.TypeSpecificSize); else if(IsEqualGUID(&s.StreamType, &ASF_StreamBufferStream)) _asf_read_media_stream(fp, psong, s.TypeSpecificSize); else if(!IsEqualGUID(&s.StreamType, &ASF_VideoStream)) { DPRINTF(E_ERROR, L_SCANNER, "Unknown asf stream type.\n"); } return 0; } static int _asf_read_extended_stream_object(FILE *fp, struct song_metadata *psong, uint32_t size) { int i, len; long off; asf_object_t tmp; asf_extended_stream_object_t xs; asf_stream_name_t nm; asf_payload_extension_t pe; if(size < sizeof(asf_extended_stream_object_t)) return -1; memset(&xs, 0, sizeof(xs)); len = sizeof(xs) - offsetof(asf_extended_stream_object_t, StartTime); if(len != fread(&xs.StartTime, 1, len, fp)) return -1; off = sizeof(xs); for(i = 0; i < xs.StreamNameCount; i++) { if(off + sizeof(nm) > size) return -1; if(sizeof(nm) != fread(&nm, 1, sizeof(nm), fp)) return -1; off += sizeof(nm); if(off + nm.Length > sizeof(asf_extended_stream_object_t)) return -1; if(nm.Length > 0) fseek(fp, nm.Length, SEEK_CUR); off += nm.Length; } for(i = 0; i < xs.PayloadExtensionSystemCount; i++) { if(off + sizeof(pe) > size) return -1; if(sizeof(pe) != fread(&pe, 1, sizeof(pe), fp)) return -1; off += sizeof(pe); if(pe.InfoLength > 0) fseek(fp, pe.InfoLength, SEEK_CUR); off += pe.InfoLength; } if(off < size) { if(sizeof(tmp) != fread(&tmp, 1, sizeof(tmp), fp)) return -1; if(IsEqualGUID(&tmp.ID, &ASF_StreamHeader)) _asf_read_stream_object(fp, psong, tmp.Size); } return 0; } static int _asf_read_header_extension(FILE *fp, struct song_metadata *psong, uint32_t size) { off_t pos; long off; asf_header_extension_t ext; asf_object_t tmp; if(size < sizeof(asf_header_extension_t)) return -1; if(sizeof(ext.Reserved1) != fread(&ext.Reserved1, 1, sizeof(ext.Reserved1), fp)) return -1; ext.Reserved2 = fget_le16(fp); ext.DataSize = fget_le32(fp); pos = ftell(fp); off = 0; while(off < ext.DataSize) { if(sizeof(asf_header_extension_t) + off > size) break; if(sizeof(tmp) != fread(&tmp, 1, sizeof(tmp), fp)) break; if(off + tmp.Size > ext.DataSize) break; if(IsEqualGUID(&tmp.ID, &ASF_ExtendedStreamPropertiesObject)) _asf_read_extended_stream_object(fp, psong, tmp.Size); off += tmp.Size; fseek(fp, pos + off, SEEK_SET); } return 0; } static int _asf_load_string(FILE *fp, int type, int size, char *buf, int len) { unsigned char data[2048]; uint16_t wc; int i, j; int16_t *wd16; int32_t *wd32; int64_t *wd64; i = 0; if(size && (size <= sizeof(data)) && (size == fread(data, 1, size, fp))) { switch(type) { case ASF_VT_UNICODE: for(j = 0; j < size; j += 2) { wd16 = (int16_t *) &data[j]; wc = (uint16_t)*wd16; i += utf16le_to_utf8(&buf[i], len - i, wc); } break; case ASF_VT_BYTEARRAY: for(i = 0; i < size; i++) { if(i + 1 >= len) break; buf[i] = data[i]; } break; case ASF_VT_BOOL: case ASF_VT_DWORD: if(size >= 4) { wd32 = (int32_t *) &data[0]; i = snprintf(buf, len, "%d", le32_to_cpu(*wd32)); } break; case ASF_VT_QWORD: if(size >= 8) { wd64 = (int64_t *) &data[0]; i = snprintf(buf, len, "%lld", (long long)le64_to_cpu(*wd64)); } break; case ASF_VT_WORD: if(size >= 2) { wd16 = (int16_t *) &data[0]; i = snprintf(buf, len, "%d", le16_to_cpu(*wd16)); } break; } size = 0; } else fseek(fp, size, SEEK_CUR); buf[i] = 0; return i; } static void * _asf_load_picture(FILE *fp, int size, void *bm, int *bm_size) { int i; char buf[256]; #if 0 // // Picture type $xx // Data length $xx $xx $xx $xx // MIME type $00 // Description $00 // Picture data char pic_type; long pic_size; pic_type = fget_byte(fp); size -= 1; pic_size = fget_le32(fp); size -= 4; #else fseek(fp, 5, SEEK_CUR); size -= 5; #endif for(i = 0; i < sizeof(buf) - 1; i++) { buf[i] = fget_le16(fp); size -= 2; if(!buf[i]) break; } buf[i] = '\0'; if(i == sizeof(buf) - 1) { while(fget_le16(fp)) size -= 2; } if(!strcasecmp(buf, "image/jpeg") || !strcasecmp(buf, "image/jpg") || !strcasecmp(buf, "image/peg")) { while(0 != fget_le16(fp)) size -= 2; if(size > 0) { if(!(bm = malloc(size))) { DPRINTF(E_ERROR, L_SCANNER, "Couldn't allocate %d bytes\n", size); } else { *bm_size = size; if(size > *bm_size || fread(bm, 1, size, fp) != size) { DPRINTF(E_ERROR, L_SCANNER, "Overrun %d bytes required\n", size); free(bm); bm = NULL; } } } else { DPRINTF(E_ERROR, L_SCANNER, "No binary data\n"); size = 0; bm = NULL; } } else { DPRINTF(E_ERROR, L_SCANNER, "Invalid mime type %s\n", buf); } *bm_size = size; return bm; } static int _get_asffileinfo(char *file, struct song_metadata *psong) { FILE *fp; asf_object_t hdr; asf_object_t tmp; unsigned long NumObjects; unsigned short TitleLength; unsigned short AuthorLength; unsigned short CopyrightLength; unsigned short DescriptionLength; unsigned short RatingLength; unsigned short NumEntries; unsigned short NameLength; unsigned short ValueType; unsigned short ValueLength; off_t pos; char buf[2048]; asf_file_properties_t FileProperties; psong->vbr_scale = -1; if(!(fp = fopen(file, "rb"))) { DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file); return -1; } if(sizeof(hdr) != fread(&hdr, 1, sizeof(hdr), fp)) { DPRINTF(E_ERROR, L_SCANNER, "Error reading %s\n", file); fclose(fp); return -1; } hdr.Size = le64_to_cpu(hdr.Size); if(!IsEqualGUID(&hdr.ID, &ASF_HeaderObject)) { DPRINTF(E_ERROR, L_SCANNER, "Not a valid header\n"); fclose(fp); return -1; } NumObjects = fget_le32(fp); fseek(fp, 2, SEEK_CUR); // Reserved le16 pos = ftell(fp); while(NumObjects > 0) { if(sizeof(tmp) != fread(&tmp, 1, sizeof(tmp), fp)) break; tmp.Size = le64_to_cpu(tmp.Size); if(pos + tmp.Size > hdr.Size) { DPRINTF(E_ERROR, L_SCANNER, "Size overrun reading header object %llu\n", (unsigned long long)tmp.Size); break; } if(IsEqualGUID(&tmp.ID, &ASF_FileProperties)) { _asf_read_file_properties(fp, &FileProperties, tmp.Size); psong->song_length = le64_to_cpu(FileProperties.PlayDuration) / 10000; psong->bitrate = le64_to_cpu(FileProperties.MaxBitrate); psong->max_bitrate = psong->bitrate; } else if(IsEqualGUID(&tmp.ID, &ASF_ContentDescription)) { TitleLength = fget_le16(fp); AuthorLength = fget_le16(fp); CopyrightLength = fget_le16(fp); DescriptionLength = fget_le16(fp); RatingLength = fget_le16(fp); if(_asf_load_string(fp, ASF_VT_UNICODE, TitleLength, buf, sizeof(buf))) { if(buf[0]) psong->title = strdup(buf); } if(_asf_load_string(fp, ASF_VT_UNICODE, AuthorLength, buf, sizeof(buf))) { if(buf[0]) psong->contributor[ROLE_TRACKARTIST] = strdup(buf); } if(CopyrightLength) fseek(fp, CopyrightLength, SEEK_CUR); if(DescriptionLength) fseek(fp, DescriptionLength, SEEK_CUR); if(RatingLength) fseek(fp, RatingLength, SEEK_CUR); } else if(IsEqualGUID(&tmp.ID, &ASF_ExtendedContentDescription)) { NumEntries = fget_le16(fp); while(NumEntries > 0) { NameLength = fget_le16(fp); _asf_load_string(fp, ASF_VT_UNICODE, NameLength, buf, sizeof(buf)); ValueType = fget_le16(fp); ValueLength = fget_le16(fp); if(!strcasecmp(buf, "AlbumTitle") || !strcasecmp(buf, "WM/AlbumTitle")) { if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf))) if(buf[0]) psong->album = strdup(buf); } else if(!strcasecmp(buf, "AlbumArtist") || !strcasecmp(buf, "WM/AlbumArtist")) { if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf))) { if(buf[0]) psong->contributor[ROLE_ALBUMARTIST] = strdup(buf); } } else if(!strcasecmp(buf, "Description") || !strcasecmp(buf, "WM/Track")) { if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf))) if(buf[0]) psong->track = atoi(buf); } else if(!strcasecmp(buf, "Genre") || !strcasecmp(buf, "WM/Genre")) { if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf))) if(buf[0]) psong->genre = strdup(buf); } else if(!strcasecmp(buf, "Year") || !strcasecmp(buf, "WM/Year")) { if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf))) if(buf[0]) psong->year = atoi(buf); } else if(!strcasecmp(buf, "WM/Director")) { if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf))) if(buf[0]) psong->contributor[ROLE_CONDUCTOR] = strdup(buf); } else if(!strcasecmp(buf, "WM/Composer")) { if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf))) if(buf[0]) psong->contributor[ROLE_COMPOSER] = strdup(buf); } else if(!strcasecmp(buf, "WM/Picture") && (ValueType == ASF_VT_BYTEARRAY)) { psong->image = _asf_load_picture(fp, ValueLength, psong->image, &psong->image_size); } else if(!strcasecmp(buf, "TrackNumber") || !strcasecmp(buf, "WM/TrackNumber")) { if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf))) if(buf[0]) psong->track = atoi(buf); } else if(!strcasecmp(buf, "isVBR")) { fseek(fp, ValueLength, SEEK_CUR); psong->vbr_scale = 0; } else if(ValueLength) { fseek(fp, ValueLength, SEEK_CUR); } NumEntries--; } } else if(IsEqualGUID(&tmp.ID, &ASF_StreamHeader)) { _asf_read_stream_object(fp, psong, tmp.Size); } else if(IsEqualGUID(&tmp.ID, &ASF_HeaderExtension)) { _asf_read_header_extension(fp, psong, tmp.Size); } pos += tmp.Size; fseek(fp, pos, SEEK_SET); NumObjects--; } #if 0 if(sizeof(hdr) == fread(&hdr, 1, sizeof(hdr), fp) && IsEqualGUID(&hdr.ID, &ASF_DataObject)) { if(psong->song_length) { psong->bitrate = (hdr.Size * 8000) / psong->song_length; } } #endif fclose(fp); return 0; } minidlna-1.1.5+dfsg/tagutils/tagutils-asf.h000066400000000000000000000242061261774340000206500ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-asf.h // DESCRIPTION : ASF (wma/wmv) metadata reader //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* * 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, see . */ #define __PACKED__ __attribute__((packed)) #ifdef HAVE_MACHINE_ENDIAN_H #include #else #include #endif typedef struct _GUID { uint32_t l; uint16_t w[2]; uint8_t b[8]; } __PACKED__ GUID; #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ GUID name = { l, { w1, w2 }, { b1, b2, b3, b4, b5, b6, b7, b8 } } #define IsEqualGUID(rguid1, rguid2) (!memcmp(rguid1, rguid2, sizeof(GUID))) #if __BYTE_ORDER == __LITTLE_ENDIAN #define SWAP32(l) (l) #define SWAP16(w) (w) #else #define SWAP32(l) ( (((l) >> 24) & 0x000000ff) | (((l) >> 8) & 0x0000ff00) | (((l) << 8) & 0x00ff0000) | (((l) << 24) & 0xff000000) ) #define SWAP16(w) ( (((w) >> 8) & 0x00ff) | (((w) << 8) & 0xff00) ) #endif DEFINE_GUID(ASF_StreamHeader, SWAP32(0xb7dc0791), SWAP16(0xa9b7), SWAP16(0x11cf), 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65); DEFINE_GUID(ASF_VideoStream, SWAP32(0xbc19efc0), SWAP16(0x5b4d), SWAP16(0x11cf), 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b); DEFINE_GUID(ASF_AudioStream, SWAP32(0xf8699e40), SWAP16(0x5b4d), SWAP16(0x11cf), 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b); DEFINE_GUID(ASF_HeaderObject, SWAP32(0x75b22630), SWAP16(0x668e), SWAP16(0x11cf), 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c); DEFINE_GUID(ASF_FileProperties, SWAP32(0x8cabdca1), SWAP16(0xa947), SWAP16(0x11cf), 0x8e, 0xe4, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65); DEFINE_GUID(ASF_ContentDescription, SWAP32(0x75b22633), SWAP16(0x668e), SWAP16(0x11cf), 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c); DEFINE_GUID(ASF_ExtendedContentDescription, SWAP32(0xd2d0a440), SWAP16(0xe307), SWAP16(0x11d2), 0x97, 0xf0, 0x00, 0xa0, 0xc9, 0x5e, 0xa8, 0x50); DEFINE_GUID(ASF_ClientGuid, SWAP32(0x8d262e32), SWAP16(0xfc28), SWAP16(0x11d7), 0xa9, 0xea, 0x00, 0x04, 0x5a, 0x6b, 0x76, 0xc2); DEFINE_GUID(ASF_HeaderExtension, SWAP32(0x5fbf03b5), SWAP16(0xa92e), SWAP16(0x11cf), 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65); DEFINE_GUID(ASF_CodecList, SWAP32(0x86d15240), SWAP16(0x311d), SWAP16(0x11d0), 0xa3, 0xa4, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6); DEFINE_GUID(ASF_DataObject, SWAP32(0x75b22636), SWAP16(0x668e), SWAP16(0x11cf), 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c); DEFINE_GUID(ASF_PaddingObject, SWAP32(0x1806d474), SWAP16(0xcadf), SWAP16(0x4509), 0xa4, 0xba, 0x9a, 0xab, 0xcb, 0x96, 0xaa, 0xe8); DEFINE_GUID(ASF_SimpleIndexObject, SWAP32(0x33000890), SWAP16(0xe5b1), SWAP16(0x11cf), 0x89, 0xf4, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xcb); DEFINE_GUID(ASF_NoErrorCorrection, SWAP32(0x20fb5700), SWAP16(0x5b55), SWAP16(0x11cf), 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b); DEFINE_GUID(ASF_AudioSpread, SWAP32(0xbfc3cd50), SWAP16(0x618f), SWAP16(0x11cf), 0x8b, 0xb2, 0x00, 0xaa, 0x00, 0xb4, 0xe2, 0x20); DEFINE_GUID(ASF_Reserved1, SWAP32(0xabd3d211), SWAP16(0xa9ba), SWAP16(0x11cf), 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65); DEFINE_GUID(ASF_Reserved2, SWAP32(0x86d15241), SWAP16(0x311d), SWAP16(0x11d0), 0xa3, 0xa4, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6); DEFINE_GUID(ASF_ContentEncryptionObject, SWAP32(0x2211B3FB), SWAP16(0xBD23), SWAP16(0x11D2), 0xB4, 0xB7, 0x00, 0xA0, 0xC9, 0x55, 0xFC, 0x6E); DEFINE_GUID(ASF_ExtendedContentEncryptionObject, SWAP32(0x298AE614), SWAP16(0x2622), SWAP16(0x4C17), 0xB9, 0x35, 0xDA, 0xE0, 0x7E, 0xE9, 0x28, 0x9C); DEFINE_GUID(ASF_ExtendedStreamPropertiesObject, SWAP32(0x14E6A5CB), SWAP16(0xC672), SWAP16(0x4332), 0x83, 0x99, 0xA9, 0x69, 0x52, 0x06, 0x5B, 0x5A); DEFINE_GUID(ASF_MediaTypeAudio, SWAP32(0x31178C9D), SWAP16(0x03E1), SWAP16(0x4528), 0xB5, 0x82, 0x3D, 0xF9, 0xDB, 0x22, 0xF5, 0x03); DEFINE_GUID(ASF_FormatTypeWave, SWAP32(0xC4C4C4D1), SWAP16(0x0049), SWAP16(0x4E2B), 0x98, 0xFB, 0x95, 0x37, 0xF6, 0xCE, 0x51, 0x6D); DEFINE_GUID(ASF_StreamBufferStream, SWAP32(0x3AFB65E2), SWAP16(0x47EF), SWAP16(0x40F2), 0xAC, 0x2C, 0x70, 0xA9, 0x0D, 0x71, 0xD3, 0x43); typedef struct _BITMAPINFOHEADER { uint32_t biSize; int32_t biWidth; int32_t biHeight; uint16_t biPlanes; uint16_t biBitCount; uint32_t biCompression; uint32_t biSizeImage; int32_t biXPelsPerMeter; int32_t biYPelsPerMeter; uint32_t biClrUsed; uint32_t biClrImportant; } __PACKED__ BITMAPINFOHEADER; typedef struct _WAVEFORMATEX { uint16_t wFormatTag; uint16_t nChannels; uint32_t nSamplesPerSec; uint32_t nAvgBytesPerSec; uint16_t nBlockAlign; uint16_t wBitsPerSample; uint16_t cbSize; } __PACKED__ WAVEFORMATEX; typedef struct _asf_stream_object_t { GUID ID; uint64_t Size; GUID StreamType; GUID ErrorCorrectionType; uint64_t TimeOffset; uint32_t TypeSpecificSize; uint32_t ErrorCorrectionSize; uint16_t StreamNumber; uint32_t Reserved; } __PACKED__ asf_stream_object_t; typedef struct _asf_media_stream_t { asf_stream_object_t Hdr; GUID MajorType; GUID SubType; uint32_t FixedSizeSamples; uint32_t TemporalCompression; uint32_t SampleSize; GUID FormatType; uint32_t FormatSize; } __PACKED__ asf_media_stream_t; typedef struct _avi_audio_format_t { uint16_t wFormatTag; uint16_t nChannels; uint32_t nSamplesPerSec; uint32_t nAvgBytesPerSec; uint16_t nBlockAlign; uint16_t wBitsPerSample; uint16_t cbSize; } __PACKED__ avi_audio_format_t; typedef struct _asf_extended_stream_object_t { GUID ID; uint64_t Size; uint64_t StartTime; uint64_t EndTime; uint32_t DataBitrate; uint32_t BufferSize; uint32_t InitialBufferFullness; uint32_t AltDataBitrate; uint32_t AltBufferSize; uint32_t AltInitialBufferFullness; uint32_t MaximumObjectSize; uint32_t Flags; uint16_t StreamNumber; uint16_t LanguageIDIndex; uint64_t AvgTimePerFrame; uint16_t StreamNameCount; uint16_t PayloadExtensionSystemCount; } __PACKED__ asf_extended_stream_object_t; typedef struct _asf_stream_name_t { uint16_t ID; uint16_t Length; } __PACKED__ asf_stream_name_t; typedef struct _asf_payload_extension_t { GUID ID; uint16_t Size; uint32_t InfoLength; } __PACKED__ asf_payload_extension_t; typedef struct _asf_object_t { GUID ID; uint64_t Size; } __PACKED__ asf_object_t; typedef struct _asf_codec_entry_t { uint16_t Type; uint16_t NameLen; uint32_t Name; uint16_t DescLen; uint32_t Desc; uint16_t InfoLen; uint32_t Info; } __PACKED__ asf_codec_entry_t; typedef struct _asf_codec_list_t { GUID ID; uint64_t Size; GUID Reserved; uint64_t NumEntries; asf_codec_entry_t Entries[2]; asf_codec_entry_t VideoCodec; } __PACKED__ asf_codec_list_t; typedef struct _asf_content_description_t { GUID ID; uint64_t Size; uint16_t TitleLength; uint16_t AuthorLength; uint16_t CopyrightLength; uint16_t DescriptionLength; uint16_t RatingLength; uint32_t Title; uint32_t Author; uint32_t Copyright; uint32_t Description; uint32_t Rating; } __PACKED__ asf_content_description_t; typedef struct _asf_file_properties_t { GUID ID; uint64_t Size; GUID FileID; uint64_t FileSize; uint64_t CreationTime; uint64_t TotalPackets; uint64_t PlayDuration; uint64_t SendDuration; uint64_t Preroll; uint32_t Flags; uint32_t MinPacketSize; uint32_t MaxPacketSize; uint32_t MaxBitrate; } __PACKED__ asf_file_properties_t; typedef struct _asf_header_extension_t { GUID ID; uint64_t Size; GUID Reserved1; uint16_t Reserved2; uint32_t DataSize; } __PACKED__ asf_header_extension_t; typedef struct _asf_video_stream_t { asf_stream_object_t Hdr; uint32_t Width; uint32_t Height; uint8_t ReservedFlags; uint16_t FormatSize; BITMAPINFOHEADER bmi; uint8_t ebih[1]; } __PACKED__ asf_video_stream_t; typedef struct _asf_audio_stream_t { asf_stream_object_t Hdr; WAVEFORMATEX wfx; } __PACKED__ asf_audio_stream_t; typedef struct _asf_payload_t { uint8_t StreamNumber; uint8_t MediaObjectNumber; uint32_t MediaObjectOffset; uint8_t ReplicatedDataLength; uint32_t ReplicatedData[2]; uint32_t PayloadLength; } __PACKED__ asf_payload_t; typedef struct _asf_packet_t { uint8_t TypeFlags; uint8_t ECFlags; uint8_t ECType; uint8_t ECCycle; uint8_t PropertyFlags; uint32_t PacketLength; uint32_t Sequence; uint32_t PaddingLength; uint32_t SendTime; uint16_t Duration; uint8_t PayloadFlags; asf_payload_t Payload; } __PACKED__ asf_packet_t; typedef struct _asf_data_object_t { GUID ID; uint64_t Size; GUID FileID; uint64_t TotalPackets; unsigned short Reserved; } __PACKED__ asf_data_object_t; typedef struct _asf_padding_object_t { GUID ID; uint64_t Size; } __PACKED__ asf_padding_object_t; typedef struct _asf_simple_index_object_t { GUID ID; uint64_t Size; GUID FileID; uint32_t IndexEntryTimeInterval; uint32_t MaximumPacketCount; uint32_t IndexEntriesCount; } __PACKED__ asf_simple_index_object_t; typedef struct _asf_header_object_t { GUID ID; uint64_t Size; uint32_t NumObjects; uint16_t Reserved; asf_header_extension_t HeaderExtension; asf_content_description_t ContentDescription; asf_file_properties_t FileProperties; asf_video_stream_t * VideoStream; asf_audio_stream_t * AudioStream; asf_codec_list_t CodecList; asf_padding_object_t PaddingObject; } __PACKED__ asf_header_object_t; #define ASF_VT_UNICODE (0) #define ASF_VT_BYTEARRAY (1) #define ASF_VT_BOOL (2) #define ASF_VT_DWORD (3) #define ASF_VT_QWORD (4) #define ASF_VT_WORD (5) static int _get_asffileinfo(char *file, struct song_metadata *psong); minidlna-1.1.5+dfsg/tagutils/tagutils-flc.c000066400000000000000000000072011261774340000206320ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-flc.c // DESCRIPTION : FLAC metadata reader //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* * 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, see . */ static int _get_flctags(char *filename, struct song_metadata *psong) { FLAC__Metadata_SimpleIterator *iterator = 0; FLAC__StreamMetadata *block; unsigned int sec, ms; int i; int err = 0; if(!(iterator = FLAC__metadata_simple_iterator_new())) { DPRINTF(E_FATAL, L_SCANNER, "Out of memory while FLAC__metadata_simple_iterator_new()\n"); return -1; } if(!FLAC__metadata_simple_iterator_init(iterator, filename, true, true)) { DPRINTF(E_ERROR, L_SCANNER, "Cannot extract tag from %s [%s]\n", filename, FLAC__Metadata_SimpleIteratorStatusString[FLAC__metadata_simple_iterator_status(iterator)]); goto _exit; } do { if(!(block = FLAC__metadata_simple_iterator_get_block(iterator))) { DPRINTF(E_ERROR, L_SCANNER, "Cannot extract tag from %s\n", filename); err = -1; goto _exit; } switch(block->type) { case FLAC__METADATA_TYPE_STREAMINFO: if (!block->data.stream_info.sample_rate) break; /* Info is crap, avoid div-by-zero. */ sec = (unsigned int)(block->data.stream_info.total_samples / block->data.stream_info.sample_rate); ms = (unsigned int)(((block->data.stream_info.total_samples % block->data.stream_info.sample_rate) * 1000) / block->data.stream_info.sample_rate); if ((sec == 0) && (ms == 0)) break; /* Info is crap, escape div-by-zero. */ psong->song_length = (sec * 1000) + ms; psong->bitrate = (((uint64_t)(psong->file_size) * 1000) / (psong->song_length / 8)); psong->samplerate = block->data.stream_info.sample_rate; psong->channels = block->data.stream_info.channels; break; case FLAC__METADATA_TYPE_VORBIS_COMMENT: for(i = 0; i < block->data.vorbis_comment.num_comments; i++) { vc_scan(psong, (char*)block->data.vorbis_comment.comments[i].entry, block->data.vorbis_comment.comments[i].length); } break; #if FLAC_API_VERSION_CURRENT >= 10 case FLAC__METADATA_TYPE_PICTURE: if (psong->image) { DPRINTF(E_MAXDEBUG, L_SCANNER, "Ignoring additional image [%s]\n", filename); break; } psong->image_size = block->data.picture.data_length; if((psong->image = malloc(psong->image_size))) memcpy(psong->image, block->data.picture.data, psong->image_size); else DPRINTF(E_ERROR, L_SCANNER, "Out of memory [%s]\n", filename); break; #endif default: break; } FLAC__metadata_object_delete(block); } while(FLAC__metadata_simple_iterator_next(iterator)); _exit: if(iterator) FLAC__metadata_simple_iterator_delete(iterator); return err; } static int _get_flcfileinfo(char *filename, struct song_metadata *psong) { psong->lossless = 1; psong->vbr_scale = 1; return 0; } minidlna-1.1.5+dfsg/tagutils/tagutils-flc.h000066400000000000000000000021641261774340000206420ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-flc.h // DESCRIPTION : FLAC metadata reader //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* 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, see . */ static int _get_flcfileinfo(char *file, struct song_metadata *psong); static int _get_flctags(char *file, struct song_metadata *psong); minidlna-1.1.5+dfsg/tagutils/tagutils-misc.c000066400000000000000000000162211261774340000210230ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-misc.c // DESCRIPTION : Misc routines for supporting tagutils //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* 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, see . */ /************************************************************************** * Language **************************************************************************/ #define MAX_ICONV_BUF 1024 typedef enum { ICONV_OK, ICONV_TRYNEXT, ICONV_FATAL } iconv_result; #ifdef HAVE_ICONV static iconv_result do_iconv(const char* to_ces, const char* from_ces, ICONV_CONST char *inbuf, size_t inbytesleft, char *outbuf_orig, size_t outbytesleft_orig) { size_t rc; iconv_result ret = ICONV_OK; size_t outbytesleft = outbytesleft_orig - 1; char* outbuf = outbuf_orig; iconv_t cd = iconv_open(to_ces, from_ces); if(cd == (iconv_t)-1) { return ICONV_FATAL; } rc = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft); if(rc == (size_t)-1) { if(errno == E2BIG) { ret = ICONV_FATAL; } else { ret = ICONV_TRYNEXT; memset(outbuf_orig, '\0', outbytesleft_orig); } } iconv_close(cd); return ret; } #else // HAVE_ICONV static iconv_result do_iconv(const char* to_ces, const char* from_ces, char *inbuf, size_t inbytesleft, char *outbuf_orig, size_t outbytesleft_orig) { return ICONV_FATAL; } #endif // HAVE_ICONV #define N_LANG_ALT 8 struct { char *lang; char *cpnames[N_LANG_ALT]; } iconv_map[] = { { "ja_JP", { "CP932", "CP950", "CP936", "ISO-8859-1", 0 } }, { "zh_CN", { "CP936", "CP950", "CP932", "ISO-8859-1", 0 } }, { "zh_TW", { "CP950", "CP936", "CP932", "ISO-8859-1", 0 } }, { "ko_KR", { "CP949", "ISO-8859-1", 0 } }, { 0, { 0 } } }; static int lang_index = -1; static int _lang2cp(char *lang) { int cp; if(!lang || lang[0] == '\0') return -1; for(cp = 0; iconv_map[cp].lang; cp++) { if(!strcasecmp(iconv_map[cp].lang, lang)) return cp; } return -2; } static unsigned char* _get_utf8_text(const id3_ucs4_t* native_text) { unsigned char *utf8_text = NULL; char *in, *in8, *iconv_buf; iconv_result rc; int i, n; in = (char*)id3_ucs4_latin1duplicate(native_text); if(!in) { goto out; } in8 = (char*)id3_ucs4_utf8duplicate(native_text); if(!in8) { free(in); goto out; } iconv_buf = (char*)calloc(MAX_ICONV_BUF, sizeof(char)); if(!iconv_buf) { free(in); free(in8); goto out; } i = lang_index; // (1) try utf8 -> default rc = do_iconv(iconv_map[i].cpnames[0], "UTF-8", in8, strlen(in8), iconv_buf, MAX_ICONV_BUF); if(rc == ICONV_OK) { utf8_text = (unsigned char*)in8; free(iconv_buf); } else if(rc == ICONV_TRYNEXT) { // (2) try default -> utf8 rc = do_iconv("UTF-8", iconv_map[i].cpnames[0], in, strlen(in), iconv_buf, MAX_ICONV_BUF); if(rc == ICONV_OK) { utf8_text = (unsigned char*)iconv_buf; } else if(rc == ICONV_TRYNEXT) { // (3) try other encodes for(n = 1; n < N_LANG_ALT && iconv_map[i].cpnames[n]; n++) { rc = do_iconv("UTF-8", iconv_map[i].cpnames[n], in, strlen(in), iconv_buf, MAX_ICONV_BUF); if(rc == ICONV_OK) { utf8_text = (unsigned char*)iconv_buf; break; } } if(!utf8_text) { // cannot iconv utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text); free(iconv_buf); } } free(in8); } free(in); out: if(!utf8_text) { utf8_text = (unsigned char*)strdup("UNKNOWN"); } return utf8_text; } static void vc_scan(struct song_metadata *psong, const char *comment, const size_t length) { char strbuf[1024]; if(length > (sizeof(strbuf) - 1)) { if( strncasecmp(comment, "LYRICS=", 7) != 0 ) { const char *eq = strchr(comment, '='); int len = 8; if (eq) len = eq - comment; DPRINTF(E_WARN, L_SCANNER, "Vorbis %.*s too long [%s]\n", len, comment, psong->path); } return; } strncpy(strbuf, comment, length); strbuf[length] = '\0'; // ALBUM, ARTIST, PUBLISHER, COPYRIGHT, DISCNUMBER, ISRC, EAN/UPN, LABEL, LABELNO, // LICENSE, OPUS, SOURCEMEDIA, TITLE, TRACKNUMBER, VERSION, ENCODED-BY, ENCODING, // -- following tags are muliples // COMPOSER, ARRANGER, LYRICIST, AUTHOR, CONDUCTOR, PERFORMER, ENSEMBLE, PART // PARTNUMBER, GENRE, DATE, LOCATION, COMMENT if(!strncasecmp(strbuf, "ALBUM=", 6)) { if( *(strbuf+6) ) psong->album = strdup(strbuf + 6); } else if(!strncasecmp(strbuf, "ARTIST=", 7)) { if( *(strbuf+7) ) psong->contributor[ROLE_ARTIST] = strdup(strbuf + 7); } else if(!strncasecmp(strbuf, "ARTISTSORT=", 11)) { psong->contributor_sort[ROLE_ARTIST] = strdup(strbuf + 11); } else if(!strncasecmp(strbuf, "ALBUMARTIST=", 12)) { if( *(strbuf+12) ) psong->contributor[ROLE_BAND] = strdup(strbuf + 12); } else if(!strncasecmp(strbuf, "ALBUMARTISTSORT=", 16)) { psong->contributor_sort[ROLE_BAND] = strdup(strbuf + 16); } else if(!strncasecmp(strbuf, "TITLE=", 6)) { if( *(strbuf+6) ) psong->title = strdup(strbuf + 6); } else if(!strncasecmp(strbuf, "TRACKNUMBER=", 12)) { psong->track = atoi(strbuf + 12); } else if(!strncasecmp(strbuf, "DISCNUMBER=", 11)) { psong->disc = atoi(strbuf + 11); } else if(!strncasecmp(strbuf, "GENRE=", 6)) { if( *(strbuf+6) ) psong->genre = strdup(strbuf + 6); } else if(!strncasecmp(strbuf, "DATE=", 5)) { if(length >= (5 + 10) && isdigit(strbuf[5 + 0]) && isdigit(strbuf[5 + 1]) && ispunct(strbuf[5 + 2]) && isdigit(strbuf[5 + 3]) && isdigit(strbuf[5 + 4]) && ispunct(strbuf[5 + 5]) && isdigit(strbuf[5 + 6]) && isdigit(strbuf[5 + 7]) && isdigit(strbuf[5 + 8]) && isdigit(strbuf[5 + 9])) { // nn-nn-yyyy strbuf[5 + 10] = '\0'; psong->year = atoi(strbuf + 5 + 6); } else { // year first. year is at most 4 digit. strbuf[5 + 4] = '\0'; psong->year = atoi(strbuf + 5); } } else if(!strncasecmp(strbuf, "COMMENT=", 8)) { if( *(strbuf+8) ) psong->comment = strdup(strbuf + 8); } else if(!strncasecmp(strbuf, "MUSICBRAINZ_ALBUMID=", 20)) { psong->musicbrainz_albumid = strdup(strbuf + 20); } else if(!strncasecmp(strbuf, "MUSICBRAINZ_TRACKID=", 20)) { psong->musicbrainz_trackid = strdup(strbuf + 20); } else if(!strncasecmp(strbuf, "MUSICBRAINZ_TRACKID=", 20)) { psong->musicbrainz_trackid = strdup(strbuf + 20); } else if(!strncasecmp(strbuf, "MUSICBRAINZ_ARTISTID=", 21)) { psong->musicbrainz_artistid = strdup(strbuf + 21); } else if(!strncasecmp(strbuf, "MUSICBRAINZ_ALBUMARTISTID=", 26)) { psong->musicbrainz_albumartistid = strdup(strbuf + 26); } } minidlna-1.1.5+dfsg/tagutils/tagutils-mp3.c000066400000000000000000000425501261774340000205730ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-mp3.c // DESCRIPTION : MP3 metadata reader //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* * 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, see . */ /* * This file is derived from mt-daap project. */ static int _get_mp3tags(char *file, struct song_metadata *psong) { struct id3_file *pid3file; struct id3_tag *pid3tag; struct id3_frame *pid3frame; int err; int index; int used; unsigned char *utf8_text; int genre = WINAMP_GENRE_UNKNOWN; int have_utf8; int have_text; id3_ucs4_t const *native_text; char *tmp; int got_numeric_genre; id3_byte_t const *image; id3_length_t image_size = 0; pid3file = id3_file_open(file, ID3_FILE_MODE_READONLY); if(!pid3file) { DPRINTF(E_ERROR, L_SCANNER, "Cannot open %s\n", file); return -1; } pid3tag = id3_file_tag(pid3file); if(!pid3tag) { err = errno; id3_file_close(pid3file); errno = err; DPRINTF(E_WARN, L_SCANNER, "Cannot get ID3 tag for %s\n", file); return -1; } index = 0; while((pid3frame = id3_tag_findframe(pid3tag, "", index))) { used = 0; utf8_text = NULL; native_text = NULL; have_utf8 = 0; have_text = 0; if(!strcmp(pid3frame->id, "YTCP")) /* for id3v2.2 */ { psong->compilation = 1; DPRINTF(E_DEBUG, L_SCANNER, "Compilation: %d [%s]\n", psong->compilation, basename(file)); } else if(!strcmp(pid3frame->id, "APIC") && !image_size) { if( (strcmp((char*)id3_field_getlatin1(&pid3frame->fields[1]), "image/jpeg") == 0) || (strcmp((char*)id3_field_getlatin1(&pid3frame->fields[1]), "image/jpg") == 0) || (strcmp((char*)id3_field_getlatin1(&pid3frame->fields[1]), "jpeg") == 0) ) { image = id3_field_getbinarydata(&pid3frame->fields[4], &image_size); if( image_size ) { psong->image = malloc(image_size); memcpy(psong->image, image, image_size); psong->image_size = image_size; //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Found thumbnail: %d\n", psong->image_size); } } } if(((pid3frame->id[0] == 'T') || (strcmp(pid3frame->id, "COMM") == 0)) && (id3_field_getnstrings(&pid3frame->fields[1]))) have_text = 1; if(have_text) { native_text = id3_field_getstrings(&pid3frame->fields[1], 0); if(native_text) { have_utf8 = 1; if(lang_index >= 0) utf8_text = _get_utf8_text(native_text); // through iconv else utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text); if(!strcmp(pid3frame->id, "TIT2")) { used = 1; psong->title = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TPE1")) { used = 1; psong->contributor[ROLE_ARTIST] = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TALB")) { used = 1; psong->album = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TCOM")) { used = 1; psong->contributor[ROLE_COMPOSER] = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TIT1")) { used = 1; psong->grouping = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TPE2")) { used = 1; psong->contributor[ROLE_BAND] = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TPE3")) { used = 1; psong->contributor[ROLE_CONDUCTOR] = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TCON")) { used = 1; psong->genre = (char*)utf8_text; got_numeric_genre = 0; if(psong->genre) { if(!strlen(psong->genre)) { genre = WINAMP_GENRE_UNKNOWN; got_numeric_genre = 1; } else if(isdigit(psong->genre[0])) { genre = atoi(psong->genre); got_numeric_genre = 1; } else if((psong->genre[0] == '(') && (isdigit(psong->genre[1]))) { genre = atoi((char*)&psong->genre[1]); got_numeric_genre = 1; } if(got_numeric_genre) { if((genre < 0) || (genre > WINAMP_GENRE_UNKNOWN)) genre = WINAMP_GENRE_UNKNOWN; free(psong->genre); psong->genre = strdup(winamp_genre[genre]); } } } else if(!strcmp(pid3frame->id, "COMM")) { used = 1; psong->comment = (char*)utf8_text; } else if(!strcmp(pid3frame->id, "TPOS")) { tmp = (char*)utf8_text; strsep(&tmp, "/"); if(tmp) { psong->total_discs = atoi(tmp); } psong->disc = atoi((char*)utf8_text); } else if(!strcmp(pid3frame->id, "TRCK")) { tmp = (char*)utf8_text; strsep(&tmp, "/"); if(tmp) { psong->total_tracks = atoi(tmp); } psong->track = atoi((char*)utf8_text); } else if(!strcmp(pid3frame->id, "TDRC")) { psong->year = atoi((char*)utf8_text); } else if(!strcmp(pid3frame->id, "TLEN")) { psong->song_length = atoi((char*)utf8_text); } else if(!strcmp(pid3frame->id, "TBPM")) { psong->bpm = atoi((char*)utf8_text); } else if(!strcmp(pid3frame->id, "TCMP")) { psong->compilation = (char)atoi((char*)utf8_text); } } } // check if text tag if((!used) && (have_utf8) && (utf8_text)) free(utf8_text); // v2 COMM if((!strcmp(pid3frame->id, "COMM")) && (pid3frame->nfields == 4)) { native_text = id3_field_getstring(&pid3frame->fields[2]); if(native_text) { utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text); if((utf8_text) && (strncasecmp((char*)utf8_text, "iTun", 4) != 0)) { // read comment free(utf8_text); native_text = id3_field_getfullstring(&pid3frame->fields[3]); if(native_text) { utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text); if(utf8_text) { free(psong->comment); psong->comment = (char*)utf8_text; } } } else { free(utf8_text); } } } index++; } id3_file_close(pid3file); //DEBUG DPRINTF(E_INFO, L_SCANNER, "Got id3 tag successfully for file=%s\n", file); return 0; } // _decode_mp3_frame static int _decode_mp3_frame(unsigned char *frame, struct mp3_frameinfo *pfi) { int ver; int layer_index; int sample_index; int bitrate_index; int samplerate_index; if((frame[0] != 0xFF) || (frame[1] < 224)) { pfi->is_valid = 0; return -1; } ver = (frame[1] & 0x18) >> 3; pfi->layer = 4 - ((frame[1] & 0x6) >> 1); layer_index = sample_index = -1; switch(ver) { case 0: pfi->mpeg_version = 0x25; // 2.5 sample_index = 2; if(pfi->layer == 1) layer_index = 3; if((pfi->layer == 2) || (pfi->layer == 3)) layer_index = 4; break; case 2: pfi->mpeg_version = 0x20; // 2.0 sample_index = 1; if(pfi->layer == 1) layer_index = 3; if((pfi->layer == 2) || (pfi->layer == 3)) layer_index = 4; break; case 3: pfi->mpeg_version = 0x10; // 1.0 sample_index = 0; if(pfi->layer == 1) layer_index = 0; if(pfi->layer == 2) layer_index = 1; if(pfi->layer == 3) layer_index = 2; break; } if((layer_index < 0) || (layer_index > 4)) { pfi->is_valid = 0; return -1; } if((sample_index < 0) || (sample_index >= 2)) { pfi->is_valid = 0; return -1; } if(pfi->layer == 1) pfi->samples_per_frame = 384; if(pfi->layer == 2) pfi->samples_per_frame = 1152; if(pfi->layer == 3) { if(pfi->mpeg_version == 0x10) pfi->samples_per_frame = 1152; else pfi->samples_per_frame = 576; } bitrate_index = (frame[2] & 0xF0) >> 4; samplerate_index = (frame[2] & 0x0C) >> 2; if((bitrate_index == 0xF) || (bitrate_index == 0x0)) { pfi->is_valid = 0; return -1; } if(samplerate_index == 3) { pfi->is_valid = 0; return -1; } pfi->bitrate = bitrate_tbl[layer_index][bitrate_index]; pfi->samplerate = sample_rate_tbl[sample_index][samplerate_index]; if((frame[3] & 0xC0 >> 6) == 3) pfi->stereo = 0; else pfi->stereo = 1; if(frame[2] & 0x02) pfi->padding = 1; else pfi->padding = 0; if(pfi->mpeg_version == 0x10) { if(pfi->stereo) pfi->xing_offset = 32; else pfi->xing_offset = 17; } else { if(pfi->stereo) pfi->xing_offset = 17; else pfi->xing_offset = 9; } pfi->crc_protected = frame[1] & 0xFE; if(pfi->layer == 1) pfi->frame_length = (12 * pfi->bitrate * 1000 / pfi->samplerate + pfi->padding) * 4; else pfi->frame_length = 144 * pfi->bitrate * 1000 / pfi->samplerate + pfi->padding; if((pfi->frame_length > 2880) || (pfi->frame_length <= 0)) { pfi->is_valid = 0; return -1; } pfi->is_valid = 1; return 0; } // _mp3_get_average_bitrate // read from midle of file, and estimate static void _mp3_get_average_bitrate(FILE *infile, struct mp3_frameinfo *pfi, const char *fname) { off_t file_size; unsigned char frame_buffer[2900]; unsigned char header[4]; int index = 0; int found = 0; off_t pos; struct mp3_frameinfo fi; int frame_count = 0; int bitrate_total = 0; fseek(infile, 0, SEEK_END); file_size = ftell(infile); pos = file_size >> 1; /* now, find the first frame */ fseek(infile, pos, SEEK_SET); if(fread(frame_buffer, 1, sizeof(frame_buffer), infile) != sizeof(frame_buffer)) return; while(!found) { while((frame_buffer[index] != 0xFF) && (index < (sizeof(frame_buffer) - 4))) index++; if(index >= (sizeof(frame_buffer) - 4)) // max mp3 framesize = 2880 { DPRINTF(E_DEBUG, L_SCANNER, "Could not find frame for %s\n", basename((char *)fname)); return; } if(!_decode_mp3_frame(&frame_buffer[index], &fi)) { /* see if next frame is valid */ fseek(infile, pos + index + fi.frame_length, SEEK_SET); if(fread(header, 1, sizeof(header), infile) != sizeof(header)) { DPRINTF(E_DEBUG, L_SCANNER, "Could not read frame header for %s\n", basename((char *)fname)); return; } if(!_decode_mp3_frame(header, &fi)) found = 1; } if(!found) index++; } pos += index; // got first frame while(frame_count < 10) { fseek(infile, pos, SEEK_SET); if(fread(header, 1, sizeof(header), infile) != sizeof(header)) { DPRINTF(E_DEBUG, L_SCANNER, "Could not read frame header for %s\n", basename((char *)fname)); return; } if(_decode_mp3_frame(header, &fi)) { DPRINTF(E_DEBUG, L_SCANNER, "Invalid frame header while averaging %s\n", basename((char *)fname)); return; } bitrate_total += fi.bitrate; frame_count++; pos += fi.frame_length; } pfi->bitrate = bitrate_total / frame_count; return; } // _mp3_get_frame_count // do brute scan static void __attribute__((unused)) _mp3_get_frame_count(FILE *infile, struct mp3_frameinfo *pfi) { int pos; int frames = 0; unsigned char frame_buffer[4]; struct mp3_frameinfo fi; off_t file_size; int err = 0; int cbr = 1; int last_bitrate = 0; fseek(infile, 0, SEEK_END); file_size = ftell(infile); pos = pfi->frame_offset; while(1) { err = 1; fseek(infile, pos, SEEK_SET); if(fread(frame_buffer, 1, sizeof(frame_buffer), infile) == sizeof(frame_buffer)) { // valid frame? if(!_decode_mp3_frame(frame_buffer, &fi)) { frames++; pos += fi.frame_length; err = 0; if((last_bitrate) && (fi.bitrate != last_bitrate)) cbr = 0; last_bitrate = fi.bitrate; // no sense to scan cbr if(cbr && (frames > 100)) { DPRINTF(E_DEBUG, L_SCANNER, "File appears to be CBR... quitting frame _mp3_get_frame_count()\n"); return; } } } if(err) { if(pos > (file_size - 4096)) { pfi->number_of_frames = frames; return; } else { DPRINTF(E_ERROR, L_SCANNER, "Frame count aborted on error. Pos=%d, Count=%d\n", pos, frames); return; } } } } // _get_mp3fileinfo static int _get_mp3fileinfo(char *file, struct song_metadata *psong) { FILE *infile; struct id3header *pid3; struct mp3_frameinfo fi; unsigned int size = 0; unsigned int n_read; off_t fp_size = 0; off_t file_size; unsigned char buffer[1024]; int index; int xing_flags; int found; int first_check = 0; char frame_buffer[4]; char id3v1taghdr[4]; if(!(infile = fopen(file, "rb"))) { DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file); return -1; } memset((void*)&fi, 0, sizeof(fi)); fseek(infile, 0, SEEK_END); file_size = ftell(infile); fseek(infile, 0, SEEK_SET); if(fread(buffer, 1, sizeof(buffer), infile) != sizeof(buffer)) { if(ferror(infile)) { DPRINTF(E_ERROR, L_SCANNER, "Error reading: %s [%s]\n", strerror(errno), file); } else { DPRINTF(E_WARN, L_SCANNER, "File too small. Probably corrupted. [%s]\n", file); } fclose(infile); return -1; } pid3 = (struct id3header*)buffer; found = 0; fp_size = 0; if(strncmp((char*)pid3->id, "ID3", 3) == 0) { char tagversion[16]; /* found an ID3 header... */ size = (pid3->size[0] << 21 | pid3->size[1] << 14 | pid3->size[2] << 7 | pid3->size[3]); fp_size = size + sizeof(struct id3header); first_check = 1; snprintf(tagversion, sizeof(tagversion), "ID3v2.%d.%d", pid3->version[0], pid3->version[1]); psong->tagversion = strdup(tagversion); } index = 0; /* Here we start the brute-force header seeking. Sure wish there * weren't so many crappy mp3 files out there */ while(!found) { fseek(infile, fp_size, SEEK_SET); if((n_read = fread(buffer, 1, sizeof(buffer), infile)) < 4) // at least mp3 frame header size (i.e. 4 bytes) { fclose(infile); return 0; } index = 0; while(!found) { while((buffer[index] != 0xFF) && (index < (n_read - 50))) index++; if((first_check) && (index)) { fp_size = 0; first_check = 0; if(n_read < sizeof(buffer)) { fclose(infile); return 0; } break; } if(index > (n_read - 50)) { fp_size += index; if(n_read < sizeof(buffer)) { fclose(infile); return 0; } break; } if(!_decode_mp3_frame(&buffer[index], &fi)) { if(!strncasecmp((char*)&buffer[index + fi.xing_offset + 4], "XING", 4)) { /* no need to check further... if there is a xing header there, * this is definately a valid frame */ found = 1; fp_size += index; } else { /* No Xing... check for next frame to validate current fram is correct */ fseek(infile, fp_size + index + fi.frame_length, SEEK_SET); if(fread(frame_buffer, 1, sizeof(frame_buffer), infile) == sizeof(frame_buffer)) { if(!_decode_mp3_frame((unsigned char*)frame_buffer, &fi)) { found = 1; fp_size += index; } } else { DPRINTF(E_ERROR, L_SCANNER, "Could not read frame header: %s\n", file); fclose(infile); return 0; } if(!found) { // cannot find second frame. Song may be too short. So assume first frame is valid. found = 1; fp_size += index; } } } if(!found) { index++; if(first_check) { DPRINTF(E_INFO, L_SCANNER, "Bad header... dropping back for full frame search [%s]\n", psong->path); first_check = 0; fp_size = 0; break; } } } } fi.frame_offset = fp_size; psong->audio_offset = fp_size; psong->audio_size = file_size - fp_size; // check if last 128 bytes is ID3v1.0 ID3v1.1 tag fseek(infile, file_size - 128, SEEK_SET); if(fread(id3v1taghdr, 1, 4, infile) == 4) { if(id3v1taghdr[0] == 'T' && id3v1taghdr[1] == 'A' && id3v1taghdr[2] == 'G') { psong->audio_size -= 128; } } if(_decode_mp3_frame(&buffer[index], &fi)) { fclose(infile); DPRINTF(E_ERROR, L_SCANNER, "Could not find sync frame: %s\n", file); return 0; } /* now check for an XING header */ psong->vbr_scale = -1; if(!strncasecmp((char*)&buffer[index + fi.xing_offset + 4], "XING", 4)) { xing_flags = buffer[index+fi.xing_offset+4+4] << 24 | buffer[index+fi.xing_offset+4+5] << 16 | buffer[index+fi.xing_offset+4+6] << 8 | buffer[index+fi.xing_offset+4+7]; psong->vbr_scale = 78; if(xing_flags & 0x1) { /* Frames field is valid... */ fi.number_of_frames = buffer[index+fi.xing_offset+4+8] << 24 | buffer[index+fi.xing_offset+4+9] << 16 | buffer[index+fi.xing_offset+4+10] << 8 | buffer[index+fi.xing_offset+4+11]; } } if((fi.number_of_frames == 0) && (!psong->song_length)) { _mp3_get_average_bitrate(infile, &fi, file); } psong->bitrate = fi.bitrate * 1000; psong->samplerate = fi.samplerate; if(!psong->song_length) { if(fi.number_of_frames) { psong->song_length = (int)((double)(fi.number_of_frames * fi.samples_per_frame * 1000.) / (double)fi.samplerate); psong->vbr_scale = 78; } else { psong->song_length = (int)((double)(file_size - fp_size) * 8. / (double)fi.bitrate); } } psong->channels = fi.stereo ? 2 : 1; fclose(infile); //DEBUG DPRINTF(E_INFO, L_SCANNER, "Got fileinfo successfully for file=%s song_length=%d\n", file, psong->song_length); psong->blockalignment = 1; xasprintf(&(psong->dlna_pn), "MP3"); return 0; } minidlna-1.1.5+dfsg/tagutils/tagutils-mp3.h000066400000000000000000000051151261774340000205740ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-mp3.h // DESCRIPTION : MP3 metadata reader //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* * 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, see . */ struct mp3_frameinfo { int layer; // 1,2,3 int bitrate; // unit=kbps int samplerate; // samp/sec int stereo; // flag int frame_length; // bytes int crc_protected; // flag int samples_per_frame; // calculated int padding; // flag int xing_offset; // for xing hdr int number_of_frames; int frame_offset; short mpeg_version; short id3_version; int is_valid; }; static int _get_mp3tags(char *file, struct song_metadata *psong); static int _get_mp3fileinfo(char *file, struct song_metadata *psong); static int _decode_mp3_frame(unsigned char *frame, struct mp3_frameinfo *pfi); // bitrate_tbl[layer_index][bitrate_index] static int bitrate_tbl[5][16] = { { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 }, /* MPEG1, L1 */ { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, /* MPEG1, L2 */ { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }, /* MPEG1, L3 */ { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 }, /* MPEG2/2.5, L1 */ { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 } /* MPEG2/2.5, L2/L3 */ }; // sample_rate[sample_index][samplerate_index] static int sample_rate_tbl[3][4] = { { 44100, 48000, 32000, 0 }, /* MPEG 1 */ { 22050, 24000, 16000, 0 }, /* MPEG 2 */ { 11025, 12000, 8000, 0 } /* MPEG 2.5 */ }; minidlna-1.1.5+dfsg/tagutils/tagutils-ogg.c000066400000000000000000000300151261774340000206410ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-ogg.c // DESCRIPTION : Ogg metadata reader //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* * 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, see . */ /* * This file is derived from mt-daap project. */ typedef struct _ogg_stream_processor { void (*process_page)(struct _ogg_stream_processor *, ogg_page *, struct song_metadata *); void (*process_end)(struct _ogg_stream_processor *, struct song_metadata *); int isillegal; int constraint_violated; int shownillegal; int isnew; long seqno; int lostseq; int start; int end; int num; char *type; ogg_uint32_t serial; ogg_stream_state os; void *data; } ogg_stream_processor; typedef struct { ogg_stream_processor *streams; int allocated; int used; int in_headers; } ogg_stream_set; typedef struct { vorbis_info vi; vorbis_comment vc; ogg_int64_t bytes; ogg_int64_t lastgranulepos; ogg_int64_t firstgranulepos; int doneheaders; } ogg_misc_vorbis_info; #define CONSTRAINT_PAGE_AFTER_EOS 1 #define CONSTRAINT_MUXING_VIOLATED 2 static ogg_stream_set * _ogg_create_stream_set(void) { ogg_stream_set *set = calloc(1, sizeof(ogg_stream_set)); set->streams = calloc(5, sizeof(ogg_stream_processor)); set->allocated = 5; set->used = 0; return set; } static void _ogg_vorbis_process(ogg_stream_processor *stream, ogg_page *page, struct song_metadata *psong) { ogg_packet packet; ogg_misc_vorbis_info *inf = stream->data; int i, header = 0; ogg_stream_pagein(&stream->os, page); if(inf->doneheaders < 3) header = 1; while(ogg_stream_packetout(&stream->os, &packet) > 0) { if(inf->doneheaders < 3) { if(vorbis_synthesis_headerin(&inf->vi, &inf->vc, &packet) < 0) { DPRINTF(E_WARN, L_SCANNER, "Could not decode vorbis header " "packet - invalid vorbis stream (%d)\n", stream->num); continue; } inf->doneheaders++; if(inf->doneheaders == 3) { if(ogg_page_granulepos(page) != 0 || ogg_stream_packetpeek(&stream->os, NULL) == 1) DPRINTF(E_WARN, L_SCANNER, "No header in vorbis stream %d\n", stream->num); DPRINTF(E_MAXDEBUG, L_SCANNER, "Vorbis headers parsed for stream %d, " "information follows...\n", stream->num); DPRINTF(E_MAXDEBUG, L_SCANNER, "Channels: %d\n", inf->vi.channels); DPRINTF(E_MAXDEBUG, L_SCANNER, "Rate: %ld\n\n", inf->vi.rate); psong->samplerate = inf->vi.rate; psong->channels = inf->vi.channels; if(inf->vi.bitrate_nominal > 0) { DPRINTF(E_MAXDEBUG, L_SCANNER, "Nominal bitrate: %f kb/s\n", (double)inf->vi.bitrate_nominal / 1000.0); psong->bitrate = inf->vi.bitrate_nominal / 1000; } else { int upper_rate, lower_rate; DPRINTF(E_MAXDEBUG, L_SCANNER, "Nominal bitrate not set\n"); // upper_rate = 0; lower_rate = 0; if(inf->vi.bitrate_upper > 0) { DPRINTF(E_MAXDEBUG, L_SCANNER, "Upper bitrate: %f kb/s\n", (double)inf->vi.bitrate_upper / 1000.0); upper_rate = inf->vi.bitrate_upper; } else { DPRINTF(E_MAXDEBUG, L_SCANNER, "Upper bitrate not set\n"); } if(inf->vi.bitrate_lower > 0) { DPRINTF(E_MAXDEBUG, L_SCANNER, "Lower bitrate: %f kb/s\n", (double)inf->vi.bitrate_lower / 1000.0); lower_rate = inf->vi.bitrate_lower;; } else { DPRINTF(E_MAXDEBUG, L_SCANNER, "Lower bitrate not set\n"); } if(upper_rate && lower_rate) { psong->bitrate = (upper_rate + lower_rate) / 2; } else { psong->bitrate = upper_rate + lower_rate; } } if(inf->vc.comments > 0) DPRINTF(E_MAXDEBUG, L_SCANNER, "User comments section follows...\n"); for(i = 0; i < inf->vc.comments; i++) { vc_scan(psong, inf->vc.user_comments[i], inf->vc.comment_lengths[i]); } } } } if(!header) { ogg_int64_t gp = ogg_page_granulepos(page); if(gp > 0) { inf->lastgranulepos = gp; } else { DPRINTF(E_WARN, L_SCANNER, "Malformed vorbis strem.\n"); } inf->bytes += page->header_len + page->body_len; } } static void _ogg_vorbis_end(ogg_stream_processor *stream, struct song_metadata *psong) { ogg_misc_vorbis_info *inf = stream->data; double bitrate, time; time = (double)inf->lastgranulepos / inf->vi.rate; bitrate = inf->bytes * 8 / time / 1000; if(psong != NULL) { if(psong->bitrate <= 0) { psong->bitrate = bitrate * 1000; } psong->song_length = time * 1000; } vorbis_comment_clear(&inf->vc); vorbis_info_clear(&inf->vi); free(stream->data); } static void _ogg_process_null(ogg_stream_processor *stream, ogg_page *page, struct song_metadata *psong) { // invalid stream } static void _ogg_process_other(ogg_stream_processor *stream, ogg_page *page, struct song_metadata *psong) { ogg_stream_pagein(&stream->os, page); } static void _ogg_free_stream_set(ogg_stream_set *set) { int i; for(i = 0; i < set->used; i++) { if(!set->streams[i].end) { // no EOS if(set->streams[i].process_end) set->streams[i].process_end(&set->streams[i], NULL); } ogg_stream_clear(&set->streams[i].os); } free(set->streams); free(set); } static int _ogg_streams_open(ogg_stream_set *set) { int i; int res = 0; for(i = 0; i < set->used; i++) { if(!set->streams[i].end) res++; } return res; } static void _ogg_null_start(ogg_stream_processor *stream) { stream->process_end = NULL; stream->type = "invalid"; stream->process_page = _ogg_process_null; } static void _ogg_other_start(ogg_stream_processor *stream, char *type) { if(type) stream->type = type; else stream->type = "unknown"; stream->process_page = _ogg_process_other; stream->process_end = NULL; } static void _ogg_vorbis_start(ogg_stream_processor *stream) { ogg_misc_vorbis_info *info; stream->type = "vorbis"; stream->process_page = _ogg_vorbis_process; stream->process_end = _ogg_vorbis_end; stream->data = calloc(1, sizeof(ogg_misc_vorbis_info)); info = stream->data; vorbis_comment_init(&info->vc); vorbis_info_init(&info->vi); } static ogg_stream_processor * _ogg_find_stream_processor(ogg_stream_set *set, ogg_page *page) { ogg_uint32_t serial = ogg_page_serialno(page); int i; int invalid = 0; int constraint = 0; ogg_stream_processor *stream; for(i = 0; i < set->used; i++) { if(serial == set->streams[i].serial) { stream = &(set->streams[i]); set->in_headers = 0; if(stream->end) { stream->isillegal = 1; stream->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS; return stream; } stream->isnew = 0; stream->start = ogg_page_bos(page); stream->end = ogg_page_eos(page); stream->serial = serial; return stream; } } if(_ogg_streams_open(set) && !set->in_headers) { constraint = CONSTRAINT_MUXING_VIOLATED; invalid = 1; } set->in_headers = 1; if(set->allocated < set->used) stream = &set->streams[set->used]; else { set->allocated += 5; set->streams = realloc(set->streams, sizeof(ogg_stream_processor) * set->allocated); stream = &set->streams[set->used]; } set->used++; stream->num = set->used; // count from 1 stream->isnew = 1; stream->isillegal = invalid; stream->constraint_violated = constraint; { int res; ogg_packet packet; ogg_stream_init(&stream->os, serial); ogg_stream_pagein(&stream->os, page); res = ogg_stream_packetout(&stream->os, &packet); if(res <= 0) { DPRINTF(E_WARN, L_SCANNER, "Invalid header page, no packet found\n"); _ogg_null_start(stream); } else if(packet.bytes >= 7 && memcmp(packet.packet, "\001vorbis", 7) == 0) _ogg_vorbis_start(stream); else if(packet.bytes >= 8 && memcmp(packet.packet, "OggMIDI\0", 8) == 0) _ogg_other_start(stream, "MIDI"); else _ogg_other_start(stream, NULL); res = ogg_stream_packetout(&stream->os, &packet); if(res > 0) { DPRINTF(E_WARN, L_SCANNER, "Invalid header page in stream %d, " "contains multiple packets\n", stream->num); } /* re-init, ready for processing */ ogg_stream_clear(&stream->os); ogg_stream_init(&stream->os, serial); } stream->start = ogg_page_bos(page); stream->end = ogg_page_eos(page); stream->serial = serial; return stream; } static int _ogg_get_next_page(FILE *f, ogg_sync_state *sync, ogg_page *page, ogg_int64_t *written) { int ret; char *buffer; int bytes; while((ret = ogg_sync_pageout(sync, page)) <= 0) { if(ret < 0) DPRINTF(E_WARN, L_SCANNER, "Hole in data found at approximate offset %lld bytes. Corrupted ogg.\n", (long long)*written); buffer = ogg_sync_buffer(sync, 4500); // chunk=4500 bytes = fread(buffer, 1, 4500, f); if(bytes <= 0) { ogg_sync_wrote(sync, 0); return 0; } ogg_sync_wrote(sync, bytes); *written += bytes; } return 1; } static int _get_oggfileinfo(char *filename, struct song_metadata *psong) { FILE *file = fopen(filename, "rb"); ogg_sync_state sync; ogg_page page; ogg_stream_set *processors = _ogg_create_stream_set(); int gotpage = 0; ogg_int64_t written = 0; if(!file) { DPRINTF(E_FATAL, L_SCANNER, "Error opening input file \"%s\": %s\n", filename, strerror(errno)); _ogg_free_stream_set(processors); return -1; } DPRINTF(E_MAXDEBUG, L_SCANNER, "Processing file \"%s\"...\n\n", filename); ogg_sync_init(&sync); while(_ogg_get_next_page(file, &sync, &page, &written)) { ogg_stream_processor *p = _ogg_find_stream_processor(processors, &page); gotpage = 1; if(!p) { DPRINTF(E_FATAL, L_SCANNER, "Could not find a processor for stream, bailing\n"); _ogg_free_stream_set(processors); fclose(file); return -1; } if(p->isillegal && !p->shownillegal) { char *constraint; switch(p->constraint_violated) { case CONSTRAINT_PAGE_AFTER_EOS: constraint = "Page found for stream after EOS flag"; break; case CONSTRAINT_MUXING_VIOLATED: constraint = "Ogg muxing constraints violated, new " "stream before EOS of all previous streams"; break; default: constraint = "Error unknown."; } DPRINTF(E_WARN, L_SCANNER, "Warning: illegally placed page(s) for logical stream %d\n" "This indicates a corrupt ogg file: %s.\n", p->num, constraint); p->shownillegal = 1; if(!p->isnew) continue; } if(p->isnew) { DPRINTF(E_MAXDEBUG, L_SCANNER, "New logical stream (#%d, serial: %08x): type %s\n", p->num, p->serial, p->type); if(!p->start) DPRINTF(E_WARN, L_SCANNER, "stream start flag not set on stream %d\n", p->num); } else if(p->start) DPRINTF(E_WARN, L_SCANNER, "stream start flag found in mid-stream " "on stream %d\n", p->num); if(p->seqno++ != ogg_page_pageno(&page)) { if(!p->lostseq) DPRINTF(E_WARN, L_SCANNER, "sequence number gap in stream %d. Got page %ld " "when expecting page %ld. Indicates missing data.\n", p->num, ogg_page_pageno(&page), p->seqno - 1); p->seqno = ogg_page_pageno(&page); p->lostseq = 1; } else p->lostseq = 0; if(!p->isillegal) { p->process_page(p, &page, psong); if(p->end) { if(p->process_end) p->process_end(p, psong); DPRINTF(E_MAXDEBUG, L_SCANNER, "Logical stream %d ended\n", p->num); p->isillegal = 1; p->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS; } } } _ogg_free_stream_set(processors); ogg_sync_clear(&sync); fclose(file); if(!gotpage) { DPRINTF(E_ERROR, L_SCANNER, "No ogg data found in file \"%s\".\n", filename); return -1; } return 0; } minidlna-1.1.5+dfsg/tagutils/tagutils-ogg.h000066400000000000000000000020701261774340000206460ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-ogg.h // DESCRIPTION : Ogg metadata reader //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* * 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, see . */ static int _get_oggfileinfo(char *filename, struct song_metadata *psong); minidlna-1.1.5+dfsg/tagutils/tagutils-pcm.c000066400000000000000000000033441261774340000206510ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-pcm.c // DESCRIPTION : Return default PCM values. //========================================================================= // Copyright (c) 2009 NETGEAR, Inc. All Rights Reserved. // based on software from Ron Pedde's FireFly Media Server project //========================================================================= /* * 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, see . */ static int _get_pcmfileinfo(char *filename, struct song_metadata *psong) { struct stat file; uint32_t sec, ms; if( stat(filename, &file) != 0 ) { DPRINTF(E_WARN, L_SCANNER, "Could not stat %s\n", filename); return -1; } psong->file_size = file.st_size; psong->bitrate = 1411200; psong->samplerate = 44100; psong->channels = 2; sec = psong->file_size / (psong->bitrate / 8); ms = ((psong->file_size % (psong->bitrate / 8)) * 1000) / (psong->bitrate / 8); psong->song_length = (sec * 1000) + ms; psong->lossless = 1; xasprintf(&(psong->mime), "audio/L16;rate=%d;channels=%d", psong->samplerate, psong->channels); xasprintf(&(psong->dlna_pn), "LPCM"); return 0; } minidlna-1.1.5+dfsg/tagutils/tagutils-pcm.h000066400000000000000000000020141261774340000206470ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-pcm.h //========================================================================= // Copyright (c) 2009- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* 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, see . */ static int _get_pcmfileinfo(char *file, struct song_metadata *psong); minidlna-1.1.5+dfsg/tagutils/tagutils-plist.c000066400000000000000000000074331261774340000212300ustar00rootroot00000000000000//========================================================================= // FILENAME : playlist.c // DESCRIPTION : Playlist //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* * 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, see . */ #include #include #include #include "tagutils.h" #include "log.h" #define MAX_BUF 4096 static FILE *fp = 0; static int _utf8bom = 0; static int _trackno; static int (*_next_track)(struct song_metadata*, struct stat*, char*, char*); static int _m3u_pls_next_track(struct song_metadata*, struct stat*, char*, char*); int start_plist(const char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type) { char *fname, *suffix; _next_track = 0; _utf8bom = 0; _trackno = 0; if(strcasecmp(type, "m3u") == 0) _next_track = _m3u_pls_next_track; else if(strcasecmp(type, "pls") == 0) _next_track = _m3u_pls_next_track; if(!_next_track) { DPRINTF(E_ERROR, L_SCANNER, "Unsupported playlist type <%s> (%s)\n", type, path); return -1; } if(!(fp = fopen(path, "rb"))) { DPRINTF(E_ERROR, L_SCANNER, "Cannot open %s\n", path); return -1; } if(!psong) return 0; memset((void*)psong, 0, sizeof(struct song_metadata)); psong->is_plist = 1; psong->path = strdup(path); psong->type = type; fname = strrchr(psong->path, '/'); psong->basename = fname ? fname + 1 : psong->path; psong->title = strdup(psong->basename); suffix = strrchr(psong->title, '.'); if(suffix) *suffix = '\0'; if(stat) { if(!psong->time_modified) psong->time_modified = stat->st_mtime; psong->file_size = stat->st_size; } return 0; } int _m3u_pls_next_track(struct song_metadata *psong, struct stat *stat, char *lang, char *type) { char buf[MAX_BUF], *p; int len; memset((void*)psong, 0, sizeof(struct song_metadata)); // read first line p = fgets(buf, MAX_BUF, fp); if(!p) { fclose(fp); return 1; } if(strcasecmp(type, "m3u") == 0) { // check BOM if(!_utf8bom && p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf') { _utf8bom = 1; p += 3; } } while(p) { while(isspace(*p)) p++; if(!(*p) || *p == '#') goto next_line; if(!isprint(*p)) { DPRINTF(E_ERROR, L_SCANNER, "Playlist looks bad (unprintable characters)\n"); fclose(fp); return 2; } if(strcasecmp(type, "pls") == 0) { // verify that it's a valid pls playlist if(!_trackno) { if(strncmp(p, "[playlist]", 10)) break; _trackno++; goto next_line; } if(strncmp(p, "File", 4) != 0) goto next_line; psong->track = strtol(p+4, &p, 10); if(!psong->track || !p++) goto next_line; _trackno = psong->track; } else if(strcasecmp(type, "m3u") == 0) psong->track = ++_trackno; len = strlen(p); while(p[len-1] == '\r' || p[len-1] == '\n') p[--len] = '\0'; psong->path = strdup(p); return 0; next_line: p = fgets(buf, MAX_BUF, fp); } fclose(fp); return 1; } int next_plist_track(struct song_metadata *psong, struct stat *stat, char *lang, char *type) { if(_next_track) return _next_track(psong, stat, lang, type); return -1; } minidlna-1.1.5+dfsg/tagutils/tagutils-wav.c000066400000000000000000000165521261774340000206740ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-wav.c // DESCRIPTION : WAV metadata reader //========================================================================= // Copyright (c) 2009 NETGEAR, Inc. All Rights Reserved. // based on software from Ron Pedde's FireFly Media Server project //========================================================================= /* * 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, see . */ #define GET_WAV_INT32(p) ((((uint8_t)((p)[3])) << 24) | \ (((uint8_t)((p)[2])) << 16) | \ (((uint8_t)((p)[1])) << 8) | \ (((uint8_t)((p)[0])))) #define GET_WAV_INT16(p) ((((uint8_t)((p)[1])) << 8) | \ (((uint8_t)((p)[0])))) static int _get_wavtags(char *filename, struct song_metadata *psong) { int fd; uint32_t len; unsigned char hdr[12]; unsigned char fmt[16]; //uint32_t chunk_data_length; uint32_t format_data_length = 0; uint32_t compression_code = 0; uint32_t channel_count = 0; uint32_t sample_rate = 0; uint32_t sample_bit_length = 0; uint32_t bit_rate; uint32_t data_length = 0; uint32_t sec, ms; uint32_t current_offset; uint32_t block_len; //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Getting WAV file info\n"); if(!(fd = open(filename, O_RDONLY))) { DPRINTF(E_WARN, L_SCANNER, "Could not create file handle\n"); return -1; } len = 12; if(!(len = read(fd, hdr, len)) || (len != 12)) { DPRINTF(E_WARN, L_SCANNER, "Could not read wav header from %s\n", filename); close(fd); return -1; } /* I've found some wav files that have INFO tags * in them... */ if(strncmp((char*)hdr + 0, "RIFF", 4) || strncmp((char*)hdr + 8, "WAVE", 4)) { DPRINTF(E_WARN, L_SCANNER, "Invalid wav header in %s\n", filename); close(fd); return -1; } //chunk_data_length = GET_WAV_INT32(hdr + 4); /* now, walk through the chunks */ current_offset = 12; while(current_offset + 8 < psong->file_size) { len = 8; if(!(len = read(fd, hdr, len)) || (len != 8)) { close(fd); DPRINTF(E_WARN, L_SCANNER, "Error reading block: %s\n", filename); return -1; } current_offset += 8; block_len = GET_WAV_INT32(hdr + 4); //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Read block %02x%02x%02x%02x (%c%c%c%c) of " // "size 0x%08x\n",hdr[0],hdr[1],hdr[2],hdr[3], // isprint(hdr[0]) ? hdr[0] : '?', // isprint(hdr[1]) ? hdr[1] : '?', // isprint(hdr[2]) ? hdr[2] : '?', // isprint(hdr[3]) ? hdr[3] : '?', // block_len); if(block_len > psong->file_size) { close(fd); DPRINTF(E_WARN, L_SCANNER, "Bad block len: %s\n", filename); return -1; } if(strncmp((char*)&hdr, "fmt ", 4) == 0) { //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Found 'fmt ' header\n"); len = 16; if(read(fd, fmt, len) != len) { close(fd); DPRINTF(E_WARN, L_SCANNER, "Bad .wav file: can't read fmt: %s\n", filename); return -1; } format_data_length = block_len; compression_code = GET_WAV_INT16(fmt); channel_count = GET_WAV_INT16(fmt + 2); sample_rate = GET_WAV_INT32(fmt + 4); sample_bit_length = GET_WAV_INT16(fmt + 14); //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Compression code: %d\n",compression_code); //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Channel count: %d\n",channel_count); //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Sample Rate: %d\n",sample_rate); //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Sample bit length %d\n",sample_bit_length); } else if(strncmp((char*)&hdr, "data", 4) == 0) { //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Found 'data' header\n"); data_length = block_len; goto next_block; } else if(strncmp((char*)&hdr, "LIST", 4) == 0) { char *tags; char *p; int off; uint32_t taglen; char **m; len = GET_WAV_INT32(hdr + 4); if(len > 65536 || len < 9) goto next_block; tags = malloc(len+1); if(!tags) goto next_block; if(read(fd, tags, len) < len || strncmp(tags, "INFO", 4) != 0) { free(tags); goto next_block; } tags[len] = '\0'; off = 4; p = tags + off; while(off < len - 8) { taglen = GET_WAV_INT32(p + 4); //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "%.*s: %.*s (%d)\n", 4, p, taglen, p + 8, taglen); m = NULL; if (taglen > 2048) { DPRINTF(E_WARN, L_SCANNER, "Ignoring long tag [%.*s] in %s\n", 4, p+8, filename); } else if(strncmp(p, "INAM", 4) == 0) m = &(psong->title); else if(strncmp(p, "IALB", 4) == 0 || strncmp(p, "IPRD", 4) == 0) m = &(psong->album); else if(strncmp(p, "IGRE", 4) == 0 || strncmp(p, "IGNR", 4) == 0) m = &(psong->genre); else if(strncmp(p, "ICMT", 4) == 0) m = &(psong->comment); else if(strncmp(p, "IART", 4) == 0) m = &(psong->contributor[ROLE_TRACKARTIST]); else if(strncmp(p, "IAAR", 4) == 0) m = &(psong->contributor[ROLE_ALBUMARTIST]); else if(strncmp(p, "ICOM", 4) == 0 || strncmp(p, "IMUS", 4) == 0) m = &(psong->contributor[ROLE_COMPOSER]); else if(strncasecmp(p, "ITRK", 4) == 0) psong->track = atoi(p + 8); else if(strncmp(p, "ICRD", 4) == 0 || strncmp(p, "IYER", 4) == 0) psong->year = atoi(p + 8); if(m) { *m = malloc(taglen + 1); strncpy(*m, p + 8, taglen); (*m)[taglen] = '\0'; } p += taglen + 8; off += taglen + 8; /* Handle some common WAV file malformations */ while (*p == '\0' && off < len) { p++; off++; } } free(tags); } next_block: lseek(fd, current_offset + block_len, SEEK_SET); current_offset += block_len; } close(fd); if(((format_data_length != 16) && (format_data_length != 18)) || (compression_code != 1) || (channel_count < 1)) { DPRINTF(E_WARN, L_SCANNER, "Invalid wav header in %s\n", filename); return -1; } if( !data_length ) data_length = psong->file_size - 44; bit_rate = sample_rate * channel_count * ((sample_bit_length + 7) / 8) * 8; psong->bitrate = bit_rate; psong->samplerate = sample_rate; psong->channels = channel_count; //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Data length: %d\n", data_length); sec = data_length / (bit_rate / 8); ms = ((data_length % (bit_rate / 8)) * 1000) / (bit_rate / 8); psong->song_length = (sec * 1000) + ms; //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Song length: %d\n", psong->song_length); //DEBUG DPRINTF(E_DEBUG,L_SCANNER,"Bit rate: %d\n", psong->bitrate); /* Upon further review, WAV files should be little-endian, and DLNA requires the LPCM profile to be big-endian. asprintf(&(psong->mime), "audio/L16;rate=%d;channels=%d", psong->samplerate, psong->channels); */ return 0; } static int _get_wavfileinfo(char *filename, struct song_metadata *psong) { psong->lossless = 1; /* Upon further review, WAV files should be little-endian, and DLNA requires the LPCM profile to be big-endian. asprintf(&(psong->dlna_pn), "LPCM"); */ return 0; } minidlna-1.1.5+dfsg/tagutils/tagutils-wav.h000066400000000000000000000021631261774340000206720ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils-wav.h // DESCRIPTION : WAV metadata reader //========================================================================= // Copyright (c) 2009- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* 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, see . */ static int _get_wavfileinfo(char *file, struct song_metadata *psong); static int _get_wavtags(char *file, struct song_metadata *psong); minidlna-1.1.5+dfsg/tagutils/tagutils.c000066400000000000000000000224371261774340000201000ustar00rootroot00000000000000//========================================================================= // FILENAME : tagutils.c // DESCRIPTION : MP3/MP4/Ogg/FLAC metadata reader //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* 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, see . */ /* This file is derived from mt-daapd project */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #ifdef HAVE_ICONV #include #endif #include #include "tagutils.h" #include "../metadata.h" #include "../utils.h" #include "../log.h" struct id3header { unsigned char id[3]; unsigned char version[2]; unsigned char flags; unsigned char size[4]; } __attribute((packed)); char *winamp_genre[] = { /*00*/ "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", /*08*/ "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", /*10*/ "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", /*18*/ "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", /*20*/ "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", /*28*/ "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", /*30*/ "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", /*38*/ "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", /*40*/ "Native American", "Cabaret", "New Wave", "Psychedelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", /*48*/ "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", /*50*/ "Folk", "Folk/Rock", "National folk", "Swing", "Fast-fusion", "Bebob", "Latin", "Revival", /*58*/ "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", /*60*/ "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera", /*68*/ "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", /*70*/ "Club", "Tango", "Samba", "Folklore", "Ballad", "Powder Ballad", "Rhythmic Soul", "Freestyle", /*78*/ "Duet", "Punk Rock", "Drum Solo", "A Capella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", /*80*/ "Club House", "Hardcore", "Terror", "Indie", "BritPop", "NegerPunk", "Polsk Punk", "Beat", /*88*/ "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary C", "Christian Rock", "Merengue", "Salsa", /*90*/ "Thrash Metal", "Anime", "JPop", "SynthPop", "Unknown" }; #define WINAMP_GENRE_UNKNOWN ((sizeof(winamp_genre) / sizeof(winamp_genre[0])) - 1) /* * Prototype */ #include "tagutils-mp3.h" #include "tagutils-aac.h" #include "tagutils-ogg.h" #include "tagutils-flc.h" #include "tagutils-asf.h" #include "tagutils-wav.h" #include "tagutils-pcm.h" static int _get_tags(char *file, struct song_metadata *psong); static int _get_fileinfo(char *file, struct song_metadata *psong); /* * Typedefs */ typedef struct { char* type; int (*get_tags)(char* file, struct song_metadata* psong); int (*get_fileinfo)(char* file, struct song_metadata* psong); } taghandler; static taghandler taghandlers[] = { { "aac", _get_aactags, _get_aacfileinfo }, { "mp3", _get_mp3tags, _get_mp3fileinfo }, { "flc", _get_flctags, _get_flcfileinfo }, { "ogg", 0, _get_oggfileinfo }, { "asf", 0, _get_asffileinfo }, { "wav", _get_wavtags, _get_wavfileinfo }, { "pcm", 0, _get_pcmfileinfo }, { NULL, 0 } }; //********************************************************************************* #include "tagutils-misc.c" #include "tagutils-mp3.c" #include "tagutils-aac.c" #include "tagutils-ogg.c" #include "tagutils-flc.c" #include "tagutils-asf.c" #include "tagutils-wav.c" #include "tagutils-pcm.c" #include "tagutils-plist.c" //********************************************************************************* // freetags() #define MAYBEFREE(a) { if((a)) free((a)); }; void freetags(struct song_metadata *psong) { int role; MAYBEFREE(psong->path); MAYBEFREE(psong->image); MAYBEFREE(psong->title); MAYBEFREE(psong->album); MAYBEFREE(psong->genre); MAYBEFREE(psong->comment); for(role = ROLE_START; role <= ROLE_LAST; role++) { MAYBEFREE(psong->contributor[role]); MAYBEFREE(psong->contributor_sort[role]); } MAYBEFREE(psong->grouping); MAYBEFREE(psong->mime); MAYBEFREE(psong->dlna_pn); MAYBEFREE(psong->tagversion); MAYBEFREE(psong->musicbrainz_albumid); MAYBEFREE(psong->musicbrainz_trackid); MAYBEFREE(psong->musicbrainz_artistid); MAYBEFREE(psong->musicbrainz_albumartistid); } // _get_fileinfo static int _get_fileinfo(char *file, struct song_metadata *psong) { taghandler *hdl; // dispatch to appropriate tag handler for(hdl = taghandlers; hdl->type; ++hdl) if(!strcmp(hdl->type, psong->type)) break; if(hdl->get_fileinfo) return hdl->get_fileinfo(file, psong); return 0; } static void _make_composite_tags(struct song_metadata *psong) { int len; len = 1; if(!psong->contributor[ROLE_ARTIST] && (psong->contributor[ROLE_BAND] || psong->contributor[ROLE_CONDUCTOR])) { if(psong->contributor[ROLE_BAND]) len += strlen(psong->contributor[ROLE_BAND]); if(psong->contributor[ROLE_CONDUCTOR]) len += strlen(psong->contributor[ROLE_CONDUCTOR]); len += 3; psong->contributor[ROLE_ARTIST] = (char*)calloc(len, 1); if(psong->contributor[ROLE_ARTIST]) { if(psong->contributor[ROLE_BAND]) strcat(psong->contributor[ROLE_ARTIST], psong->contributor[ROLE_BAND]); if(psong->contributor[ROLE_BAND] && psong->contributor[ROLE_CONDUCTOR]) strcat(psong->contributor[ROLE_ARTIST], " - "); if(psong->contributor[ROLE_CONDUCTOR]) strcat(psong->contributor[ROLE_ARTIST], psong->contributor[ROLE_CONDUCTOR]); } } #if 0 // already taken care of by scanner.c if(!psong->title) { char *suffix; psong->title = strdup(psong->basename); suffix = strrchr(psong->title, '.'); if(suffix) *suffix = '\0'; } #endif } /*****************************************************************************/ // _get_tags static int _get_tags(char *file, struct song_metadata *psong) { taghandler *hdl; // dispatch for(hdl = taghandlers ; hdl->type ; ++hdl) if(!strcasecmp(hdl->type, psong->type)) break; if(hdl->get_tags) { return hdl->get_tags(file, psong); } return 0; } /*****************************************************************************/ // readtags int readtags(char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type) { char *fname; if(lang_index == -1) lang_index = _lang2cp(lang); memset((void*)psong, 0, sizeof(struct song_metadata)); psong->path = strdup(path); psong->type = type; fname = strrchr(psong->path, '/'); psong->basename = fname ? fname + 1 : psong->path; if(stat) { if(!psong->time_modified) psong->time_modified = stat->st_mtime; psong->file_size = stat->st_size; } // get tag if( _get_tags(path, psong) == 0 ) { _make_composite_tags(psong); } // get fileinfo return _get_fileinfo(path, psong); } minidlna-1.1.5+dfsg/tagutils/tagutils.h000066400000000000000000000067611261774340000201070ustar00rootroot00000000000000//========================================================================= // FILENAME : taguilts.h // DESCRIPTION : Header for tagutils.c //========================================================================= // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. //========================================================================= /* * 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, see . */ /* * This file is derived from mt-daap project. */ #ifndef _TAG_H_ #define _TAG_H_ #include #include #include #include #include #define ROLE_NOUSE 0 #define ROLE_START 1 #define ROLE_ARTIST 1 #define ROLE_TRACKARTIST 2 #define ROLE_ALBUMARTIST 3 #define ROLE_BAND 4 #define ROLE_CONDUCTOR 5 #define ROLE_COMPOSER 6 #define ROLE_LAST 6 #define N_ROLE 7 struct song_metadata { int file_size; char *dirpath; char *path; char *basename; // basename is part of path char *type; int time_modified; uint8_t *image; // coverart int image_size; char *title; // TIT2 char *album; // TALB char *genre; // TCON char *comment; // COMM char *contributor[N_ROLE]; // TPE1 (artist) // TCOM (composer) // TPE3 (conductor) // TPE2 (orchestra) char *contributor_sort[N_ROLE]; char *grouping; // TIT1 int year; // TDRC int track; // TRCK int total_tracks; // TRCK int disc; // TPOS int total_discs; // TPOS int bpm; // TBPM char compilation; // YTCP int bitrate; int max_bitrate; int samplerate; int samplesize; int channels; int song_length; // TLEN int audio_size; int audio_offset; int vbr_scale; int lossless; int blockalignment; char *mime; // MIME type char *dlna_pn; // DLNA Profile Name char *tagversion; unsigned long album_id; unsigned long track_id; unsigned long genre_id; unsigned long contributor_id[N_ROLE]; char *musicbrainz_albumid; char *musicbrainz_trackid; char *musicbrainz_artistid; char *musicbrainz_albumartistid; int is_plist; int plist_position; int plist_id; }; #define WMA 0x161 #define WMAPRO 0x162 #define WMALSL 0x163 extern int scan_init(char *path); extern void make_composite_tags(struct song_metadata *psong); extern int readtags(char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type); extern void freetags(struct song_metadata *psong); extern int start_plist(const char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type); extern int next_plist_track(struct song_metadata *psong, struct stat *stat, char *lang, char *type); #endif minidlna-1.1.5+dfsg/testupnpdescgen.c000066400000000000000000000077501261774340000176240ustar00rootroot00000000000000/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * * Copyright (c) 2006-2008, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "config.h" #include "upnpdescgen.h" char uuidvalue[] = "uuid:12345678-0000-0000-0000-00000000abcd"; char friendly_name[] = "localhost: system_type"; char serialnumber[] = "12345678"; char modelname[] = "MiniDLNA"; char modelnumber[] = "1"; char presentationurl[] = "http://192.168.0.1:8080/"; unsigned int updateID = 0; #if PNPX char pnpx_hwid[] = "VEN_01F2&DEV_0101&REV_01 VEN_0033&DEV_0001&REV_01"; #endif int getifaddr(const char * ifname, char * buf, int len) { strncpy(buf, "1.2.3.4", len); return 0; } int upnp_get_portmapping_number_of_entries() { return 42; } /* To be improved */ int xml_pretty_print(const char * s, int len, FILE * f) { int n = 0, i; int elt_close = 0; int c, indent = 0; while(len > 0) { c = *(s++); len--; switch(c) { case '<': if(len>0 && *s == '/') elt_close++; else if(len>0 && *s == '?') elt_close = 1; else elt_close = 0; if(elt_close!=1) { if(elt_close > 1) indent--; fputc('\n', f); n++; for(i=indent; i>0; i--) fputc(' ', f); n += indent; } fputc(c, f); n++; break; case '>': fputc(c, f); n++; if(elt_close==1) { /*fputc('\n', f); n++; */ //elt_close = 0; if(indent > 0) indent--; } else if(elt_close == 0) indent++; break; default: fputc(c, f); n++; } } return n; } /* stupid test */ const char * str1 = "Prefix123String"; const char * str2 = "123String"; void stupid_test() { printf("str1:'%s' str2:'%s'\n", str1, str2); printf("str1:%p str2:%p str2-str1:%ld\n", str1, str2, (long)(str2-str1)); } /* main */ int main(int argc, char * * argv) { char * rootDesc; int rootDescLen; char * s; int l; rootDesc = genRootDesc(&rootDescLen); xml_pretty_print(rootDesc, rootDescLen, stdout); free(rootDesc); printf("\n----------------\n"); printf("ContentDirectory\n"); printf("----------------\n"); s = genContentDirectory(&l); xml_pretty_print(s, l, stdout); free(s); printf("\n----------------\n"); printf("ConnectionManager\n"); printf("----------------\n"); s = genConnectionManager(&l); xml_pretty_print(s, l, stdout); free(s); printf("\n----------------\n"); printf("X_MS_MRR\n"); printf("----------------\n"); s = genX_MS_MediaReceiverRegistrar(&l); xml_pretty_print(s, l, stdout); free(s); printf("\n-------------\n"); /* stupid_test(); */ return 0; } minidlna-1.1.5+dfsg/tivo_beacon.c000066400000000000000000000205271261774340000166760ustar00rootroot00000000000000/* * Linux/C based server for TiVo Home Media Option protocol * * Based on version 1.5.1 of * "TiVo Connect Automatic Machine; Discovery Protocol Specification" * Based on version 1.1.0 of * "TiVo Home Media Option; Music and Photos Server Protocol Specification" * * Dave Clemans, April 2003 * * byRequest TiVo HMO Server * Copyright (C) 2003 Dave Clemans * * This file is based on byRequest, and is part of MiniDLNA. * * byRequest 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. * * byRequest 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 byRequest. If not, see . */ #include "config.h" #ifdef TIVO_SUPPORT #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tivo_beacon.h" #include "upnpglobalvars.h" #include "log.h" /* OpenAndConfHTTPSocket() : * setup the socket used to handle incoming HTTP connections. */ int OpenAndConfTivoBeaconSocket() { int s; int i = 1; struct sockaddr_in beacon; if( (s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { DPRINTF(E_ERROR, L_TIVO, "socket(http): %s\n", strerror(errno)); return -1; } if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) { DPRINTF(E_WARN, L_TIVO, "setsockopt(http, SO_REUSEADDR): %s\n", strerror(errno)); } memset(&beacon, 0, sizeof(struct sockaddr_in)); beacon.sin_family = AF_INET; beacon.sin_port = htons(2190); beacon.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(s, (struct sockaddr *)&beacon, sizeof(struct sockaddr_in)) < 0) { DPRINTF(E_ERROR, L_TIVO, "bind(http): %s\n", strerror(errno)); close(s); return -1; } i = 1; if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &i, sizeof(i)) < 0 ) { DPRINTF(E_WARN, L_TIVO, "setsockopt(http, SO_BROADCAST): %s\n", strerror(errno)); close(s); return -1; } return s; } /* * Returns the interface broadcast address to be used for beacons */ uint32_t getBcastAddress(void) { int i, rval; int s; struct sockaddr_in sin; struct sockaddr_in addr; struct ifreq ifr[16]; struct ifconf ifc; int count = 0; uint32_t ret = INADDR_BROADCAST; s = socket(PF_INET, SOCK_STREAM, 0); if (!s) return ret; memset(&ifc, '\0', sizeof(ifc)); ifc.ifc_len = sizeof(ifr); ifc.ifc_req = ifr; if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { DPRINTF(E_ERROR, L_TIVO, "Error getting interface list [%s]\n", strerror(errno)); close(s); return ret; } count = ifc.ifc_len / sizeof(struct ifreq); for (i = 0; i < count; i++) { memcpy(&addr, &ifr[i].ifr_addr, sizeof(addr)); if(strcmp(inet_ntoa(addr.sin_addr), lan_addr[0].str) == 0) { rval = ioctl(s, SIOCGIFBRDADDR, &ifr[i]); if( rval < 0 ) { DPRINTF(E_ERROR, L_TIVO, "Failed to get broadcast addr on %s [%s]\n", ifr[i].ifr_name, strerror(errno)); break; } memcpy(&sin, &ifr[i].ifr_broadaddr, sizeof(sin)); DPRINTF(E_DEBUG, L_TIVO, "Interface: %s broadcast addr %s\n", ifr[i].ifr_name, inet_ntoa(sin.sin_addr)); ret = ntohl((uint32_t)(sin.sin_addr.s_addr)); break; } } close(s); return ret; } /* * Send outgoing beacon to the specified address * This will either be a specific or broadcast address */ void sendBeaconMessage(int fd, struct sockaddr_in * client, int len, int broadcast) { char msg[512]; int msg_len; msg_len = snprintf(msg, sizeof(msg), "TiVoConnect=1\n" "swversion=1.0\n" "method=%s\n" "identity=%s\n" "machine=%s\n" "platform=pc/minidlna\n" "services=TiVoMediaServer:%d/http\n", broadcast ? "broadcast" : "connected", uuidvalue, friendly_name, runtime_vars.port); if (msg_len < 0) return; DPRINTF(E_DEBUG, L_TIVO, "Sending TiVo beacon to %s\n", inet_ntoa(client->sin_addr)); sendto(fd, msg, msg_len, 0, (struct sockaddr*)client, len); } /* * Parse and save a received beacon packet from another server, or from * a TiVo. * * Returns true if this was a broadcast beacon msg */ static int rcvBeaconMessage(char *beacon) { char *tivoConnect = NULL; char *method = NULL; char *identity = NULL; char *machine = NULL; char *platform = NULL; char *services = NULL; char *cp; char *scp; char *tokptr; cp = strtok_r(beacon, "=\r\n", &tokptr); while( cp != NULL ) { scp = cp; cp = strtok_r(NULL, "=\r\n", &tokptr); if( strcasecmp(scp, "tivoconnect") == 0 ) tivoConnect = cp; else if( strcasecmp(scp, "method") == 0 ) method = cp; else if( strcasecmp(scp, "identity") == 0 ) identity = cp; else if( strcasecmp(scp, "machine") == 0 ) machine = cp; else if( strcasecmp(scp, "platform") == 0 ) platform = cp; else if( strcasecmp(scp, "services") == 0 ) services = cp; cp = strtok_r(NULL, "=\r\n", &tokptr); } if( !tivoConnect || !platform || !method ) return 0; #ifdef DEBUG static struct aBeacon *topBeacon = NULL; struct aBeacon *b; time_t current; int len; char buf[32]; static time_t lastSummary = 0; current = time(NULL); for( b = topBeacon; b != NULL; b = b->next ) { if( strcasecmp(machine, b->machine) == 0 || strcasecmp(identity, b->identity) == 0 ) break; } if( b == NULL ) { b = calloc(1, sizeof(*b)); if( machine ) b->machine = strdup(machine); if( identity ) b->identity = strdup(identity); b->next = topBeacon; topBeacon = b; DPRINTF(E_DEBUG, L_TIVO, "Received new beacon: machine(%s) platform(%s) services(%s)\n", machine ? machine : "-", platform ? platform : "-", services ? services : "-" ); } b->lastSeen = current; if( !lastSummary ) lastSummary = current; if( lastSummary + 1800 < current ) { /* Give a summary of received server beacons every half hour or so */ len = 0; for( b = topBeacon; b != NULL; b = b->next ) { len += strlen(b->machine) + 32; } scp = malloc(len + 128); strcpy( scp, "Known servers: " ); for( b = topBeacon; b != NULL; b = b->next ) { strcat(scp, b->machine); sprintf(buf, "(%ld)", current - b->lastSeen); strcat(scp, buf); if( b->next != NULL ) strcat(scp, ","); } strcat(scp, "\n"); DPRINTF(E_DEBUG, L_TIVO, "%s\n", scp); free(scp); lastSummary = current; } #endif /* It's pointless to respond to a non-TiVo beacon. */ if( strncmp(platform, "tcd/", 4) != 0 ) return 0; if( strcasecmp(method, "broadcast") == 0 ) { DPRINTF(E_DEBUG, L_TIVO, "Received new beacon: machine(%s/%s) platform(%s) services(%s)\n", machine ? machine : "-", identity ? identity : "-", platform ? platform : "-", services ? services : "-" ); return 1; } return 0; } void ProcessTiVoBeacon(int s) { int n; char *cp; struct sockaddr_in sendername; socklen_t len_r; char bufr[1500]; len_r = sizeof(struct sockaddr_in); /* We only expect to see beacon msgs from TiVo's and possibly other tivo servers */ n = recvfrom(s, bufr, sizeof(bufr), 0, (struct sockaddr *)&sendername, &len_r); if( n > 0 ) bufr[n] = '\0'; /* find which subnet the client is in */ for(n = 0; n. */ #include "config.h" #ifdef TIVO_SUPPORT /* * * A saved copy of a beacon from another tivo or another server * */ struct aBeacon { #ifdef DEBUG time_t lastSeen; #endif char * machine; char * identity; struct aBeacon *next; }; uint32_t getBcastAddress(); int OpenAndConfTivoBeaconSocket(); void sendBeaconMessage(int fd, struct sockaddr_in * client, int len, int broadcast); void ProcessTiVoBeacon(int fd); #endif minidlna-1.1.5+dfsg/tivo_commands.c000066400000000000000000000514121261774340000172450ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include "config.h" #ifdef TIVO_SUPPORT #include #include #include #include #include #include #include "tivo_utils.h" #include "upnpglobalvars.h" #include "upnphttp.h" #include "upnpsoap.h" #include "utils.h" #include "sql.h" #include "log.h" static void SendRootContainer(struct upnphttp *h) { char *resp; int len; len = xasprintf(&resp, "\n" "" "
" "x-container/tivo-server" "x-container/folder" "0" "3" "%s" "
" "0" "3" "" "
" "x-container/tivo-photos" "x-container/folder" "Pictures on %s" "
" "" "" "/TiVoConnect?Command=QueryContainer&Container=3" "" "" "
" "" "
" "x-container/tivo-music" "x-container/folder" "Music on %s" "
" "" "" "/TiVoConnect?Command=QueryContainer&Container=1" "" "" "
" "" "
" "x-container/tivo-videos" "x-container/folder" "Videos on %s" "
" "" "" "/TiVoConnect?Command=QueryContainer&Container=2" "x-container/tivo-videos" "" "" "
" "
", friendly_name, friendly_name, friendly_name, friendly_name); BuildResp_upnphttp(h, resp, len); free(resp); SendResp_upnphttp(h); } static void SendFormats(struct upnphttp *h, const char *sformat) { char *resp; int len; len = xasprintf(&resp, "" "" "" "video/x-tivo-mpeg" "" "" "" "%s" "" "" "", sformat); BuildResp_upnphttp(h, resp, len); free(resp); SendResp_upnphttp(h); } static char * tivo_unescape_tag(char *tag) { modifyString(tag, "&amp;", "&", 1); modifyString(tag, "&amp;lt;", "<", 1); modifyString(tag, "&lt;", "<", 1); modifyString(tag, "&amp;gt;", ">", 1); modifyString(tag, "&gt;", ">", 1); modifyString(tag, "&quot;", """, 1); return tag; } #define FLAG_SEND_RESIZED 0x01 #define FLAG_NO_PARAMS 0x02 #define FLAG_VIDEO 0x04 static int callback(void *args, int argc, char **argv, char **azColName) { struct Response *passed_args = (struct Response *)args; char *id = argv[0], *class = argv[1], *detailID = argv[2], *size = argv[3], *title = argv[4], *duration = argv[5], *bitrate = argv[6], *sampleFrequency = argv[7], *artist = argv[8], *album = argv[9], *genre = argv[10], *comment = argv[11], *date = argv[12], *resolution = argv[13], *mime = argv[14]; struct string_s *str = passed_args->str; if( strncmp(class, "item", 4) == 0 ) { int flags = 0; tivo_unescape_tag(title); if( strncmp(mime, "audio", 5) == 0 ) { flags |= FLAG_NO_PARAMS; strcatf(str, "
" "%s" "%s" "%s", "audio/*", mime, size); strcatf(str, "%s", title); if( date ) strcatf(str, "%.*s", 4, date); } else if( strcmp(mime, "image/jpeg") == 0 ) { flags |= FLAG_SEND_RESIZED; strcatf(str, "
" "%s" "%s" "%s", "image/*", mime, size); if( date ) { struct tm tm; memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; // Have libc figure out if DST is in effect or not strptime(date, "%Y-%m-%dT%H:%M:%S", &tm); strcatf(str, "0x%X", (unsigned int)mktime(&tm)); } if( comment ) strcatf(str, "%s", comment); } else if( strncmp(mime, "video", 5) == 0 ) { char *episode; flags |= FLAG_VIDEO; strcatf(str, "
" "%s" "%s" "%s", mime, mime, size); episode = strstr(title, " - "); if( episode ) { strcatf(str, "%s", episode+3); *episode = '\0'; } if( date ) { struct tm tm; memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; // Have libc figure out if DST is in effect or not strptime(date, "%Y-%m-%dT%H:%M:%S", &tm); strcatf(str, "0x%X", (unsigned int)mktime(&tm)); } if( comment ) strcatf(str, "%s", tivo_unescape_tag(comment)); } else { return 0; } strcatf(str, "%s", title); if( artist ) { strcatf(str, "%s", tivo_unescape_tag(artist)); } if( album ) { strcatf(str, "%s", tivo_unescape_tag(album)); } if( genre ) { strcatf(str, "%s", tivo_unescape_tag(genre)); } if( resolution ) { char *width = strsep(&resolution, "x"); strcatf(str, "%s" "%s", width, resolution); } if( duration ) { strcatf(str, "%d", atoi(strrchr(duration, '.')+1) + (1000*atoi(strrchr(duration, ':')+1)) + (60000*atoi(strrchr(duration, ':')-2)) + (3600000*atoi(duration))); } if( bitrate ) { strcatf(str, "%s", bitrate); } if( sampleFrequency ) { strcatf(str, "%s", sampleFrequency); } strcatf(str, "
" "" "%s" "/%s/%s.%s%s" "", mime, (flags & FLAG_SEND_RESIZED) ? "Resized" : "MediaItems", detailID, mime_to_ext(mime), (flags & FLAG_NO_PARAMS) ? "No" : ""); if( flags & FLAG_VIDEO ) { strcatf(str, "" "image/*" "urn:tivo:image:save-until-i-delete-recording" ""); } strcatf(str, ""); } else if( strncmp(class, "container", 9) == 0 ) { int count; /* Determine the number of children */ #ifdef __sparc__ /* Adding filters on large containers can take a long time on slow processors */ count = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", id); #else count = sql_get_int_field(db, "SELECT count(*) from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID) where PARENT_ID = '%s' and " " (MIME in ('image/jpeg', 'audio/mpeg', 'video/mpeg', 'video/x-tivo-mpeg', 'video/x-tivo-mpeg-ts')" " or CLASS glob 'container*')", id); #endif strcatf(str, "" "
" "x-container/folder" "x-container/folder" "%s" "%d" "
" "" "" "/TiVoConnect?Command=QueryContainer&Container=%s" "x-tivo-container/folder" "" "", tivo_unescape_tag(title), count, id); } strcatf(str, "
"); passed_args->returned++; return 0; } #define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.CLASS, o.DETAIL_ID, d.SIZE, d.TITLE," \ " d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST, d.ALBUM, d.GENRE," \ " d.COMMENT, d.DATE, d.RESOLUTION, d.MIME, d.DISC, d.TRACK " static void SendItemDetails(struct upnphttp *h, int64_t item) { char *sql; char *zErrMsg = NULL; struct Response args; struct string_s str; int ret; memset(&args, 0, sizeof(args)); memset(&str, 0, sizeof(str)); str.data = malloc(32768); str.size = 32768; str.off = sprintf(str.data, "\n"); args.str = &str; args.requested = 1; xasprintf(&sql, SELECT_COLUMNS "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" " where o.DETAIL_ID = %lld group by o.DETAIL_ID", (long long)item); DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); free(sql); if( ret != SQLITE_OK ) { DPRINTF(E_ERROR, L_HTTP, "SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } strcatf(&str, ""); BuildResp_upnphttp(h, str.data, str.off); free(str.data); SendResp_upnphttp(h); } static void SendContainer(struct upnphttp *h, const char *objectID, int itemStart, int itemCount, char *anchorItem, int anchorOffset, int recurse, char *sortOrder, char *filter, unsigned long int randomSeed) { char *resp = malloc(262144); char *sql, *item, *saveptr; char *zErrMsg = NULL; char **result; char *title, *which; char what[10], order[96]={0}, order2[96]={0}, myfilter[256]={0}; char str_buf[1024]; char type[8]; char groupBy[19] = {0}; struct Response args; struct string_s str; int totalMatches = 0; int i, ret; memset(&args, 0, sizeof(args)); memset(&str, 0, sizeof(str)); args.str = &str; str.data = resp+1024; str.size = 262144-1024; if( itemCount >= 0 ) { args.requested = itemCount; } else { if( itemCount == -100 ) itemCount = 1; args.requested = itemCount * -1; } switch( *objectID ) { case '1': strcpy(type, "music"); break; case '2': strcpy(type, "videos"); break; case '3': strcpy(type, "photos"); break; default: strcpy(type, "server"); break; } if( strlen(objectID) == 1 ) { switch( *objectID ) { case '1': xasprintf(&title, "Music on %s", friendly_name); break; case '2': xasprintf(&title, "Videos on %s", friendly_name); break; case '3': xasprintf(&title, "Pictures on %s", friendly_name); break; default: xasprintf(&title, "Unknown on %s", friendly_name); break; } } else { item = sql_get_text_field(db, "SELECT NAME from OBJECTS where OBJECT_ID = '%q'", objectID); if( item ) { title = escape_tag(item, 1); sqlite3_free(item); } else title = strdup("UNKNOWN"); } if( recurse ) { which = sqlite3_mprintf("OBJECT_ID glob '%q$*'", objectID); strcpy(groupBy, "group by DETAIL_ID"); } else { which = sqlite3_mprintf("PARENT_ID = '%q'", objectID); } if( sortOrder ) { if( strcasestr(sortOrder, "Random") ) { sprintf(order, "tivorandom(%lu)", randomSeed); if( itemCount < 0 ) sprintf(order2, "tivorandom(%lu) DESC", randomSeed); else sprintf(order2, "tivorandom(%lu)", randomSeed); } else { short title_state = 0; item = strtok_r(sortOrder, ",", &saveptr); while( item != NULL ) { int reverse=0; if( *item == '!' ) { reverse = 1; item++; } if( strcasecmp(item, "Type") == 0 ) { strcat(order, "CLASS"); strcat(order2, "CLASS"); } else if( strcasecmp(item, "Title") == 0 ) { /* Explicitly sort music by track then title. */ if( title_state < 2 && *objectID == '1' ) { if( !title_state ) { strcat(order, "DISC"); strcat(order2, "DISC"); title_state = 1; } else { strcat(order, "TRACK"); strcat(order2, "TRACK"); title_state = 2; } } else { strcat(order, "TITLE"); strcat(order2, "TITLE"); title_state = -1; } } else if( strcasecmp(item, "CreationDate") == 0 || strcasecmp(item, "CaptureDate") == 0 ) { strcat(order, "DATE"); strcat(order2, "DATE"); } else { DPRINTF(E_INFO, L_TIVO, "Unhandled SortOrder [%s]\n", item); goto unhandled_order; } if( reverse ) { strcat(order, " DESC"); if( itemCount >= 0 ) strcat(order2, " DESC"); else strcat(order2, " ASC"); } else { strcat(order, " ASC"); if( itemCount >= 0 ) strcat(order2, " ASC"); else strcat(order2, " DESC"); } strcat(order, ", "); strcat(order2, ", "); unhandled_order: if( title_state <= 0 ) item = strtok_r(NULL, ",", &saveptr); } if( title_state != -1 ) { strcat(order, "TITLE ASC, "); if( itemCount >= 0 ) strcat(order2, "TITLE ASC, "); else strcat(order2, "TITLE DESC, "); } strcat(order, "DETAIL_ID ASC"); if( itemCount >= 0 ) strcat(order2, "DETAIL_ID ASC"); else strcat(order2, "DETAIL_ID DESC"); } } else { sprintf(order, "CLASS, NAME, DETAIL_ID"); if( itemCount < 0 ) sprintf(order2, "CLASS DESC, NAME DESC, DETAIL_ID DESC"); else sprintf(order2, "CLASS, NAME, DETAIL_ID"); } if( filter ) { item = strtok_r(filter, ",", &saveptr); for( i=0; item != NULL; i++ ) { if( i ) { strcat(myfilter, " or "); } if( (strcasecmp(item, "x-container/folder") == 0) || (strncasecmp(item, "x-tivo-container/", 17) == 0) ) { strcat(myfilter, "CLASS glob 'container*'"); } else if( strncasecmp(item, "image", 5) == 0 ) { strcat(myfilter, "MIME = 'image/jpeg'"); } else if( strncasecmp(item, "audio", 5) == 0 ) { strcat(myfilter, "MIME = 'audio/mpeg'"); } else if( strncasecmp(item, "video", 5) == 0 ) { strcat(myfilter, "MIME in ('video/mpeg', 'video/x-tivo-mpeg', 'video/x-tivo-mpeg-ts')"); } else { DPRINTF(E_INFO, L_TIVO, "Unhandled Filter [%s]\n", item); if( i ) { ret = strlen(myfilter); myfilter[ret-4] = '\0'; } i--; } item = strtok_r(NULL, ",", &saveptr); } } else { strcpy(myfilter, "MIME in ('image/jpeg', 'audio/mpeg', 'video/mpeg', 'video/x-tivo-mpeg', 'video/x-tivo-mpeg-ts') or CLASS glob 'container*'"); } if( anchorItem ) { if( strstr(anchorItem, "QueryContainer") ) { strcpy(what, "OBJECT_ID"); saveptr = strrchr(anchorItem, '='); if( saveptr ) anchorItem = saveptr + 1; } else { strcpy(what, "DETAIL_ID"); } sqlite3Prng.isInit = 0; sql = sqlite3_mprintf("SELECT %s from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)" " where %s and (%s)" " %s" " order by %s", what, which, myfilter, groupBy, order2); DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql); if( (sql_get_table(db, sql, &result, &ret, NULL) == SQLITE_OK) && ret ) { for( i=1; i<=ret; i++ ) { if( strcmp(anchorItem, result[i]) == 0 ) { if( itemCount < 0 ) itemStart = ret - i + itemCount; else itemStart += i; break; } } sqlite3_free_table(result); } sqlite3_free(sql); } args.start = itemStart+anchorOffset; sqlite3Prng.isInit = 0; ret = sql_get_int_field(db, "SELECT count(distinct DETAIL_ID) " "from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)" " where %s and (%s)", which, myfilter); totalMatches = (ret > 0) ? ret : 0; if( itemCount < 0 && !itemStart && !anchorOffset ) { args.start = totalMatches + itemCount; } sql = sqlite3_mprintf(SELECT_COLUMNS "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" " where %s and (%s)" " %s" " order by %s limit %d, %d", which, myfilter, groupBy, order, args.start, args.requested); DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); sqlite3_free(sql); if( ret != SQLITE_OK ) { DPRINTF(E_ERROR, L_HTTP, "SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); Send500(h); sqlite3_free(which); free(title); free(resp); return; } strcatf(&str, ""); ret = sprintf(str_buf, "\n" "" "
" "x-container/tivo-%s" "x-container/folder" "%d" "%s" "
" "%d" "%d", type, totalMatches, title, args.start, args.returned); str.data -= ret; memcpy(str.data, &str_buf, ret); str.size = str.off+ret; free(title); sqlite3_free(which); BuildResp_upnphttp(h, str.data, str.size); free(resp); SendResp_upnphttp(h); } void ProcessTiVoCommand(struct upnphttp *h, const char *orig_path) { char *path; char *key, *val; char *saveptr = NULL, *item; char *command = NULL, *container = NULL, *anchorItem = NULL; char *sortOrder = NULL, *filter = NULL, *sformat = NULL; int64_t detailItem=0; int itemStart=0, itemCount=-100, anchorOffset=0, recurse=0; unsigned long int randomSeed=0; path = strdup(orig_path); DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo command %s\n", path); item = strtok_r( path, "&", &saveptr ); while( item != NULL ) { if( *item == '\0' ) { item = strtok_r( NULL, "&", &saveptr ); continue; } decodeString(item, 1); val = item; key = strsep(&val, "="); decodeString(val, 1); DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val); if( strcasecmp(key, "Command") == 0 ) { command = val; } else if( strcasecmp(key, "Container") == 0 ) { container = val; } else if( strcasecmp(key, "ItemStart") == 0 ) { itemStart = atoi(val); } else if( strcasecmp(key, "ItemCount") == 0 ) { itemCount = atoi(val); } else if( strcasecmp(key, "AnchorItem") == 0 ) { anchorItem = basename(val); } else if( strcasecmp(key, "AnchorOffset") == 0 ) { anchorOffset = atoi(val); } else if( strcasecmp(key, "Recurse") == 0 ) { recurse = strcasecmp("yes", val) == 0 ? 1 : 0; } else if( strcasecmp(key, "SortOrder") == 0 ) { sortOrder = val; } else if( strcasecmp(key, "Filter") == 0 ) { filter = val; } else if( strcasecmp(key, "RandomSeed") == 0 ) { randomSeed = strtoul(val, NULL, 10); } else if( strcasecmp(key, "Url") == 0 ) { if( val ) detailItem = strtoll(basename(val), NULL, 10); } else if( strcasecmp(key, "SourceFormat") == 0 ) { sformat = val; } else if( strcasecmp(key, "Format") == 0 || // Only send XML strcasecmp(key, "SerialNum") == 0 || // Unused for now strcasecmp(key, "DoGenres") == 0 ) // Not sure what this is, so ignore it { ;; } else { DPRINTF(E_DEBUG, L_GENERAL, "Unhandled parameter [%s]\n", key); } item = strtok_r( NULL, "&", &saveptr ); } if( anchorItem ) { strip_ext(anchorItem); } if( command ) { if( strcmp(command, "QueryContainer") == 0 ) { if( !container || (strcmp(container, "/") == 0) ) { SendRootContainer(h); } else { SendContainer(h, container, itemStart, itemCount, anchorItem, anchorOffset, recurse, sortOrder, filter, randomSeed); } } else if( strcmp(command, "QueryItem") == 0 ) { SendItemDetails(h, detailItem); } else if( strcmp(command, "QueryFormats") == 0 ) { SendFormats(h, sformat); } else { DPRINTF(E_DEBUG, L_GENERAL, "Unhandled command [%s]\n", command); Send501(h); free(path); return; } } free(path); CloseSocket_upnphttp(h); } #endif // TIVO_SUPPORT minidlna-1.1.5+dfsg/tivo_commands.h000066400000000000000000000016421261774340000172520ustar00rootroot00000000000000/* TiVo command processing * * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Justin Maggard * * MiniDLNA media server * Copyright (C) 2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include "config.h" #ifdef TIVO_SUPPORT void ProcessTiVoCommand(struct upnphttp *h, const char *orig_path); #endif minidlna-1.1.5+dfsg/tivo_utils.c000066400000000000000000000066201261774340000166050ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include "config.h" #ifdef TIVO_SUPPORT #include #include #include #include #include #include #include #include #include "tivo_utils.h" /* This function based on byRequest */ char * decodeString(char *string, int inplace) { if( !string ) return NULL; int alloc = (int)strlen(string)+1; char *ns = NULL; unsigned char in; int strindex=0; long hex; if( !inplace ) { if( !(ns = malloc(alloc)) ) return NULL; } while(--alloc > 0) { in = *string; if((in == '%') && isxdigit(string[1]) && isxdigit(string[2])) { /* this is two hexadecimal digits following a '%' */ char hexstr[3]; char *ptr; hexstr[0] = string[1]; hexstr[1] = string[2]; hexstr[2] = 0; hex = strtol(hexstr, &ptr, 16); in = (unsigned char)hex; /* this long is never bigger than 255 anyway */ if( inplace ) { *string = in; memmove(string+1, string+3, alloc-2); } else { string+=2; } alloc-=2; } if( !inplace ) ns[strindex++] = in; string++; } if( inplace ) { free(ns); return string; } else { ns[strindex] = '\0'; /* terminate it */ return ns; } } /* These next functions implement a repeatable random function with a user-provided seed */ static int seedRandomByte(uint32_t seed) { unsigned char t; if( !sqlite3Prng.isInit ) { int i; char k[256]; sqlite3Prng.j = 0; sqlite3Prng.i = 0; memset(&k, '\0', sizeof(k)); memcpy(&k, &seed, 4); for(i=0; i<256; i++) sqlite3Prng.s[i] = i; for(i=0; i<256; i++) { sqlite3Prng.j += sqlite3Prng.s[i] + k[i]; t = sqlite3Prng.s[sqlite3Prng.j]; sqlite3Prng.s[sqlite3Prng.j] = sqlite3Prng.s[i]; sqlite3Prng.s[i] = t; } sqlite3Prng.isInit = 1; } /* Generate and return single random byte */ sqlite3Prng.i++; t = sqlite3Prng.s[sqlite3Prng.i]; sqlite3Prng.j += t; sqlite3Prng.s[sqlite3Prng.i] = sqlite3Prng.s[sqlite3Prng.j]; sqlite3Prng.s[sqlite3Prng.j] = t; t += sqlite3Prng.s[sqlite3Prng.i]; return sqlite3Prng.s[t]; } static void seedRandomness(int n, void *pbuf, uint32_t seed) { unsigned char *zbuf = pbuf; while( n-- ) *(zbuf++) = seedRandomByte(seed); } void TiVoRandomSeedFunc(sqlite3_context *context, int argc, sqlite3_value **argv) { int64_t r, seed; if( argc != 1 || sqlite3_value_type(argv[0]) != SQLITE_INTEGER ) return; seed = sqlite3_value_int64(argv[0]); seedRandomness(sizeof(r), &r, seed); sqlite3_result_int64(context, r); } int is_tivo_file(const char *path) { unsigned char buf[5]; unsigned char hdr[5] = { 'T','i','V','o','\0' }; int fd; /* read file header */ fd = open(path, O_RDONLY); if( fd < 0 ) return 0; if( read(fd, buf, 5) < 5 ) buf[0] = 'X'; close(fd); return !memcmp(buf, hdr, 5); } #endif minidlna-1.1.5+dfsg/tivo_utils.h000066400000000000000000000024231261774340000166070ustar00rootroot00000000000000/* TiVo helper functions * * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Justin Maggard * * MiniDLNA media server * Copyright (C) 2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include "config.h" #ifdef TIVO_SUPPORT #include struct sqlite3PrngType { unsigned char isInit; /* True if initialized */ unsigned char i, j; /* State variables */ unsigned char s[256]; /* State variables */ } sqlite3Prng; char * decodeString(char *string, int inplace); void TiVoRandomSeedFunc(sqlite3_context *context, int argc, sqlite3_value **argv); int is_tivo_file(const char *path); #else #define decodeString(X, Y) ({}) #endif minidlna-1.1.5+dfsg/upnpdescgen.c000066400000000000000000000526721261774340000167270ustar00rootroot00000000000000/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * * Copyright (c) 2006-2008, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "config.h" #include "getifaddr.h" #include "upnpdescgen.h" #include "minidlnapath.h" #include "upnpglobalvars.h" #undef DESC_DEBUG static const char * const upnptypes[] = { "string", "boolean", "ui2", "ui4", "i4", "uri", "int", "bin.base64" }; static const char * const upnpdefaultvalues[] = { 0, "Unconfigured" }; static const char * const upnpallowedvalues[] = { 0, /* 0 */ "DSL", /* 1 */ "POTS", "Cable", "Ethernet", 0, "Up", /* 6 */ "Down", "Initializing", "Unavailable", 0, "TCP", /* 11 */ "UDP", 0, "Unconfigured", /* 14 */ "IP_Routed", "IP_Bridged", 0, "Unconfigured", /* 18 */ "Connecting", "Connected", "PendingDisconnect", "Disconnecting", "Disconnected", 0, "ERROR_NONE", /* 25 */ 0, "OK", /* 27 */ "ContentFormatMismatch", "InsufficientBandwidth", "UnreliableChannel", "Unknown", 0, "Input", /* 33 */ "Output", 0, "BrowseMetadata", /* 36 */ "BrowseDirectChildren", 0, "COMPLETED", /* 39 */ "ERROR", "IN_PROGRESS", "STOPPED", 0, RESOURCE_PROTOCOL_INFO_VALUES, /* 44 */ 0, "0", /* 46 */ 0, "", /* 48 */ 0 }; static const char xmlver[] = "\r\n"; static const char root_service[] = "scpd xmlns=\"urn:schemas-upnp-org:service-1-0\""; static const char root_device[] = "root xmlns=\"urn:schemas-upnp-org:device-1-0\"" #if PNPX " xmlns:pnpx=\"http://schemas.microsoft.com/windows/pnpx/2005/11\"" " xmlns:df=\"http://schemas.microsoft.com/windows/2008/09/devicefoundation\"" #endif ; /* root Description of the UPnP Device */ static const struct XMLElt rootDesc[] = { {root_device, INITHELPER(1,2)}, {"specVersion", INITHELPER(3,2)}, {"device", INITHELPER(5,(14+PNPX))}, {"/major", "1"}, {"/minor", "0"}, {"/deviceType", "urn:schemas-upnp-org:device:MediaServer:1"}, #if PNPX == 5 {"/pnpx:X_hardwareId", pnpx_hwid}, {"/pnpx:X_compatibleId", "MS_DigitalMediaDeviceClass_DMS_V001"}, {"/pnpx:X_deviceCategory", "MediaDevices"}, {"/df:X_deviceCategory", "Multimedia.DMS"}, {"/microsoft:magicPacketWakeSupported xmlns:microsoft=\"urn:schemas-microsoft-com:WMPNSS-1-0\"", "0"}, #endif {"/friendlyName", friendly_name}, /* required */ {"/manufacturer", ROOTDEV_MANUFACTURER}, /* required */ {"/manufacturerURL", ROOTDEV_MANUFACTURERURL}, /* optional */ {"/modelDescription", ROOTDEV_MODELDESCRIPTION}, /* recommended */ {"/modelName", modelname}, /* required */ {"/modelNumber", modelnumber}, {"/modelURL", ROOTDEV_MODELURL}, {"/serialNumber", serialnumber}, {"/UDN", uuidvalue}, /* required */ {"/dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\"", "DMS-1.50"}, {"/presentationURL", presentationurl}, /* recommended */ {"iconList", INITHELPER((19+PNPX),4)}, {"serviceList", INITHELPER((43+PNPX),3)}, {"icon", INITHELPER((23+PNPX),5)}, {"icon", INITHELPER((28+PNPX),5)}, {"icon", INITHELPER((33+PNPX),5)}, {"icon", INITHELPER((38+PNPX),5)}, {"/mimetype", "image/png"}, {"/width", "48"}, {"/height", "48"}, {"/depth", "24"}, {"/url", "/icons/sm.png"}, {"/mimetype", "image/png"}, {"/width", "120"}, {"/height", "120"}, {"/depth", "24"}, {"/url", "/icons/lrg.png"}, {"/mimetype", "image/jpeg"}, {"/width", "48"}, {"/height", "48"}, {"/depth", "24"}, {"/url", "/icons/sm.jpg"}, {"/mimetype", "image/jpeg"}, {"/width", "120"}, {"/height", "120"}, {"/depth", "24"}, {"/url", "/icons/lrg.jpg"}, {"service", INITHELPER((46+PNPX),5)}, {"service", INITHELPER((51+PNPX),5)}, {"service", INITHELPER((56+PNPX),5)}, {"/serviceType", "urn:schemas-upnp-org:service:ContentDirectory:1"}, {"/serviceId", "urn:upnp-org:serviceId:ContentDirectory"}, {"/controlURL", CONTENTDIRECTORY_CONTROLURL}, {"/eventSubURL", CONTENTDIRECTORY_EVENTURL}, {"/SCPDURL", CONTENTDIRECTORY_PATH}, {"/serviceType", "urn:schemas-upnp-org:service:ConnectionManager:1"}, {"/serviceId", "urn:upnp-org:serviceId:ConnectionManager"}, {"/controlURL", CONNECTIONMGR_CONTROLURL}, {"/eventSubURL", CONNECTIONMGR_EVENTURL}, {"/SCPDURL", CONNECTIONMGR_PATH}, {"/serviceType", "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"}, {"/serviceId", "urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar"}, {"/controlURL", X_MS_MEDIARECEIVERREGISTRAR_CONTROLURL}, {"/eventSubURL", X_MS_MEDIARECEIVERREGISTRAR_EVENTURL}, {"/SCPDURL", X_MS_MEDIARECEIVERREGISTRAR_PATH}, {0, 0} }; /* For ConnectionManager */ static const struct argument GetProtocolInfoArgs[] = { {"Source", 2, 0}, {"Sink", 2, 1}, {NULL, 0, 0} }; static const struct argument PrepareForConnectionArgs[] = { {"RemoteProtocolInfo", 1, 6}, {"PeerConnectionManager", 1, 4}, {"PeerConnectionID", 1, 7}, {"Direction", 1, 5}, {"ConnectionID", 2, 7}, {"AVTransportID", 2, 8}, {"RcsID", 2, 9}, {NULL, 0, 0} }; static const struct argument ConnectionCompleteArgs[] = { {"ConnectionID", 1, 7}, {NULL, 0, 0} }; static const struct argument GetCurrentConnectionIDsArgs[] = { {"ConnectionIDs", 2, 2}, {NULL, 0, 0} }; static const struct argument GetCurrentConnectionInfoArgs[] = { {"ConnectionID", 1, 7}, {"RcsID", 2, 9}, {"AVTransportID", 2, 8}, {"ProtocolInfo", 2, 6}, {"PeerConnectionManager", 2, 4}, {"PeerConnectionID", 2, 7}, {"Direction", 2, 5}, {"Status", 2, 3}, {NULL, 0, 0} }; static const struct action ConnectionManagerActions[] = { {"GetProtocolInfo", GetProtocolInfoArgs}, /* R */ //OPTIONAL {"PrepareForConnection", PrepareForConnectionArgs}, /* R */ //OPTIONAL {"ConnectionComplete", ConnectionCompleteArgs}, /* R */ {"GetCurrentConnectionIDs", GetCurrentConnectionIDsArgs}, /* R */ {"GetCurrentConnectionInfo", GetCurrentConnectionInfoArgs}, /* R */ {0, 0} }; static const struct stateVar ConnectionManagerVars[] = { {"SourceProtocolInfo", 0|EVENTED, 0, 0, 44}, /* required */ {"SinkProtocolInfo", 0|EVENTED, 0, 0, 48}, /* required */ {"CurrentConnectionIDs", 0|EVENTED, 0, 0, 46}, /* required */ {"A_ARG_TYPE_ConnectionStatus", 0, 0, 27}, /* required */ {"A_ARG_TYPE_ConnectionManager", 0, 0}, /* required */ {"A_ARG_TYPE_Direction", 0, 0, 33}, /* required */ {"A_ARG_TYPE_ProtocolInfo", 0, 0}, /* required */ {"A_ARG_TYPE_ConnectionID", 4, 0}, /* required */ {"A_ARG_TYPE_AVTransportID", 4, 0}, /* required */ {"A_ARG_TYPE_RcsID", 4, 0}, /* required */ {0, 0} }; static const struct argument GetSearchCapabilitiesArgs[] = { {"SearchCaps", 2, 10}, {0, 0} }; static const struct argument GetSortCapabilitiesArgs[] = { {"SortCaps", 2, 11}, {0, 0} }; static const struct argument GetSystemUpdateIDArgs[] = { {"Id", 2, 12}, {0, 0} }; static const struct argument BrowseArgs[] = { {"ObjectID", 1, 1}, {"BrowseFlag", 1, 4}, {"Filter", 1, 5}, {"StartingIndex", 1, 7}, {"RequestedCount", 1, 8}, {"SortCriteria", 1, 6}, {"Result", 2, 2}, {"NumberReturned", 2, 8}, {"TotalMatches", 2, 8}, {"UpdateID", 2, 9}, {0, 0} }; static const struct argument SearchArgs[] = { {"ContainerID", 1, 1}, {"SearchCriteria", 1, 3}, {"Filter", 1, 5}, {"StartingIndex", 1, 7}, {"RequestedCount", 1, 8}, {"SortCriteria", 1, 6}, {"Result", 2, 2}, {"NumberReturned", 2, 8}, {"TotalMatches", 2, 8}, {"UpdateID", 2, 9}, {0, 0} }; static const struct action ContentDirectoryActions[] = { {"GetSearchCapabilities", GetSearchCapabilitiesArgs}, /* R */ {"GetSortCapabilities", GetSortCapabilitiesArgs}, /* R */ {"GetSystemUpdateID", GetSystemUpdateIDArgs}, /* R */ {"Browse", BrowseArgs}, /* R */ {"Search", SearchArgs}, /* O */ #if 0 // Not implementing optional features yet... {"CreateObject", CreateObjectArgs}, /* O */ {"DestroyObject", DestroyObjectArgs}, /* O */ {"UpdateObject", UpdateObjectArgs}, /* O */ {"ImportResource", ImportResourceArgs}, /* O */ {"ExportResource", ExportResourceArgs}, /* O */ {"StopTransferResource", StopTransferResourceArgs}, /* O */ {"GetTransferProgress", GetTransferProgressArgs}, /* O */ {"DeleteResource", DeleteResourceArgs}, /* O */ {"CreateReference", CreateReferenceArgs}, /* O */ #endif {0, 0} }; static const struct stateVar ContentDirectoryVars[] = { {"TransferIDs", 0|EVENTED, 0, 0, 48}, /* 0 */ {"A_ARG_TYPE_ObjectID", 0, 0}, {"A_ARG_TYPE_Result", 0, 0}, {"A_ARG_TYPE_SearchCriteria", 0, 0}, {"A_ARG_TYPE_BrowseFlag", 0, 0, 36}, /* Allowed Values : BrowseMetadata / BrowseDirectChildren */ {"A_ARG_TYPE_Filter", 0, 0}, /* 5 */ {"A_ARG_TYPE_SortCriteria", 0, 0}, {"A_ARG_TYPE_Index", 3, 0}, {"A_ARG_TYPE_Count", 3, 0}, {"A_ARG_TYPE_UpdateID", 3, 0}, {"SearchCapabilities", 0, 0}, {"SortCapabilities", 0, 0}, {"SystemUpdateID", 3|EVENTED, 0, 0, 255}, {0, 0} }; static const struct argument GetIsAuthorizedArgs[] = { {"DeviceID", 1, 0}, {"Result", 2, 3}, {NULL, 0, 0} }; static const struct argument GetIsValidatedArgs[] = { {"DeviceID", 1, 0}, {"Result", 2, 3}, {NULL, 0, 0} }; static const struct argument GetRegisterDeviceArgs[] = { {"RegistrationReqMsg", 1, 1}, {"RegistrationRespMsg", 2, 2}, {NULL, 0, 0} }; static const struct action X_MS_MediaReceiverRegistrarActions[] = { {"IsAuthorized", GetIsAuthorizedArgs}, /* R */ {"IsValidated", GetIsValidatedArgs}, /* R */ {"RegisterDevice", GetRegisterDeviceArgs}, {0, 0} }; static const struct stateVar X_MS_MediaReceiverRegistrarVars[] = { {"A_ARG_TYPE_DeviceID", 0, 0}, {"A_ARG_TYPE_RegistrationReqMsg", 7, 0}, {"A_ARG_TYPE_RegistrationRespMsg", 7, 0}, {"A_ARG_TYPE_Result", 6, 0}, {"AuthorizationDeniedUpdateID", 3|EVENTED, 0}, {"AuthorizationGrantedUpdateID", 3|EVENTED, 0}, {"ValidationRevokedUpdateID", 3|EVENTED, 0}, {"ValidationSucceededUpdateID", 3|EVENTED, 0}, {0, 0} }; static const struct serviceDesc scpdContentDirectory = { ContentDirectoryActions, ContentDirectoryVars }; static const struct serviceDesc scpdConnectionManager = { ConnectionManagerActions, ConnectionManagerVars }; static const struct serviceDesc scpdX_MS_MediaReceiverRegistrar = { X_MS_MediaReceiverRegistrarActions, X_MS_MediaReceiverRegistrarVars }; /* strcat_str() * concatenate the string and use realloc to increase the * memory buffer if needed. */ static char * strcat_str(char * str, int * len, int * tmplen, const char * s2) { char *p; int s2len; s2len = (int)strlen(s2); if(*tmplen <= (*len + s2len)) { if(s2len < 256) *tmplen += 256; else *tmplen += s2len + 1; p = realloc(str, *tmplen); if (!p) { if(s2len < 256) *tmplen -= 256; else *tmplen -= s2len + 1; return str; } else str = p; } /*strcpy(str + *len, s2); */ memcpy(str + *len, s2, s2len + 1); *len += s2len; return str; } /* strcat_char() : * concatenate a character and use realloc to increase the * size of the memory buffer if needed */ static char * strcat_char(char * str, int * len, int * tmplen, char c) { char *p; if(*tmplen <= (*len + 1)) { *tmplen += 256; p = (char *)realloc(str, *tmplen); if (!p) { *tmplen -= 256; return str; } else str = p; } str[*len] = c; (*len)++; return str; } /* iterative subroutine using a small stack * This way, the progam stack usage is kept low */ static char * genXML(char * str, int * len, int * tmplen, const struct XMLElt * p) { uint16_t i, j, k; int top; const char * eltname, *s; char c; char element[64]; struct { unsigned short i; unsigned short j; const char * eltname; } pile[16]; /* stack */ top = -1; i = 0; /* current node */ j = 1; /* i + number of nodes*/ for(;;) { eltname = p[i].eltname; if(!eltname) return str; if(eltname[0] == '/') { #ifdef DESC_DEBUG printf("DBG: <%s>%s<%s>\n", eltname+1, p[i].data, eltname); #endif str = strcat_char(str, len, tmplen, '<'); str = strcat_str(str, len, tmplen, eltname+1); str = strcat_char(str, len, tmplen, '>'); str = strcat_str(str, len, tmplen, p[i].data); str = strcat_char(str, len, tmplen, '<'); sscanf(eltname, "%s", element); str = strcat_str(str, len, tmplen, element); str = strcat_char(str, len, tmplen, '>'); for(;;) { if(top < 0) return str; i = ++(pile[top].i); j = pile[top].j; #ifdef DESC_DEBUG printf("DBG: pile[%d]\t%d %d\n", top, i, j); #endif if(i==j) { #ifdef DESC_DEBUG printf("DBG: i==j, \n", pile[top].eltname); #endif str = strcat_char(str, len, tmplen, '<'); str = strcat_char(str, len, tmplen, '/'); s = pile[top].eltname; for(c = *s; c > ' '; c = *(++s)) str = strcat_char(str, len, tmplen, c); str = strcat_char(str, len, tmplen, '>'); top--; } else break; } } else { #ifdef DESC_DEBUG printf("DBG: [%d] <%s>\n", i, eltname); #endif str = strcat_char(str, len, tmplen, '<'); str = strcat_str(str, len, tmplen, eltname); str = strcat_char(str, len, tmplen, '>'); k = i; /*i = p[k].index; */ /*j = i + p[k].nchild; */ i = (unsigned long)p[k].data & 0xffff; j = i + ((unsigned long)p[k].data >> 16); top++; #ifdef DESC_DEBUG printf("DBG: +pile[%d]\t%d %d\n", top, i, j); #endif pile[top].i = i; pile[top].j = j; pile[top].eltname = eltname; } } } /* genRootDesc() : * - Generate the root description of the UPnP device. * - the len argument is used to return the length of * the returned string. * - tmp_uuid argument is used to build the uuid string */ char * genRootDesc(int * len) { char * str; int tmplen; tmplen = 2560; str = (char *)malloc(tmplen); if(str == NULL) return NULL; * len = strlen(xmlver); memcpy(str, xmlver, *len + 1); str = genXML(str, len, &tmplen, rootDesc); str[*len] = '\0'; return str; } char * genRootDescSamsung(int * len) { char * str; int tmplen; struct XMLElt samsungRootDesc[sizeof(rootDesc)/sizeof(struct XMLElt)]; tmplen = 2560; str = (char *)malloc(tmplen); if(str == NULL) return NULL; * len = strlen(xmlver); memcpy(str, xmlver, *len + 1); /* Replace the optional modelURL and manufacturerURL fields with Samsung foo */ memcpy(&samsungRootDesc, &rootDesc, sizeof(rootDesc)); samsungRootDesc[8+PNPX].eltname = "/sec:ProductCap"; samsungRootDesc[8+PNPX].data = "smi,DCM10,getMediaInfo.sec,getCaptionInfo.sec"; samsungRootDesc[12+PNPX].eltname = "/sec:X_ProductCap"; samsungRootDesc[12+PNPX].data = "smi,DCM10,getMediaInfo.sec,getCaptionInfo.sec"; str = genXML(str, len, &tmplen, samsungRootDesc); str[*len] = '\0'; return str; } /* genServiceDesc() : * Generate service description with allowed methods and * related variables. */ static char * genServiceDesc(int * len, const struct serviceDesc * s) { int i, j; const struct action * acts; const struct stateVar * vars; const struct argument * args; const char * p; char * str; int tmplen; tmplen = 2048; str = (char *)malloc(tmplen); if(str == NULL) return NULL; /*strcpy(str, xmlver); */ *len = strlen(xmlver); memcpy(str, xmlver, *len + 1); acts = s->actionList; vars = s->serviceStateTable; str = strcat_char(str, len, &tmplen, '<'); str = strcat_str(str, len, &tmplen, root_service); str = strcat_char(str, len, &tmplen, '>'); str = strcat_str(str, len, &tmplen, "10"); i = 0; str = strcat_str(str, len, &tmplen, ""); while(acts[i].name) { str = strcat_str(str, len, &tmplen, ""); str = strcat_str(str, len, &tmplen, acts[i].name); str = strcat_str(str, len, &tmplen, ""); /* argument List */ args = acts[i].args; if(args) { str = strcat_str(str, len, &tmplen, ""); j = 0; while(args[j].dir) { str = strcat_str(str, len, &tmplen, ""); p = vars[args[j].relatedVar].name; str = strcat_str(str, len, &tmplen, (args[j].name ? args[j].name : p)); str = strcat_str(str, len, &tmplen, ""); str = strcat_str(str, len, &tmplen, args[j].dir==1?"in":"out"); str = strcat_str(str, len, &tmplen, ""); str = strcat_str(str, len, &tmplen, p); str = strcat_str(str, len, &tmplen, ""); j++; } str = strcat_str(str, len, &tmplen,""); } str = strcat_str(str, len, &tmplen, ""); /*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */ i++; } str = strcat_str(str, len, &tmplen, ""); i = 0; while(vars[i].name) { str = strcat_str(str, len, &tmplen, ""); str = strcat_str(str, len, &tmplen, vars[i].name); str = strcat_str(str, len, &tmplen, ""); str = strcat_str(str, len, &tmplen, upnptypes[vars[i].itype & 0x0f]); str = strcat_str(str, len, &tmplen, ""); if(vars[i].iallowedlist) { str = strcat_str(str, len, &tmplen, ""); for(j=vars[i].iallowedlist; upnpallowedvalues[j]; j++) { str = strcat_str(str, len, &tmplen, ""); str = strcat_str(str, len, &tmplen, upnpallowedvalues[j]); str = strcat_str(str, len, &tmplen, ""); } str = strcat_str(str, len, &tmplen, ""); } /*if(vars[i].defaultValue) */ if(vars[i].idefault) { str = strcat_str(str, len, &tmplen, ""); /*str = strcat_str(str, len, &tmplen, vars[i].defaultValue); */ str = strcat_str(str, len, &tmplen, upnpdefaultvalues[vars[i].idefault]); str = strcat_str(str, len, &tmplen, ""); } str = strcat_str(str, len, &tmplen, ""); /*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */ i++; } str = strcat_str(str, len, &tmplen, ""); str[*len] = '\0'; return str; } /* genContentDirectory() : * Generate the ContentDirectory xml description */ char * genContentDirectory(int * len) { return genServiceDesc(len, &scpdContentDirectory); } /* genConnectionManager() : * Generate the ConnectionManager xml description */ char * genConnectionManager(int * len) { return genServiceDesc(len, &scpdConnectionManager); } /* genX_MS_MediaReceiverRegistrar() : * Generate the X_MS_MediaReceiverRegistrar xml description */ char * genX_MS_MediaReceiverRegistrar(int * len) { return genServiceDesc(len, &scpdX_MS_MediaReceiverRegistrar); } static char * genEventVars(int * len, const struct serviceDesc * s, const char * servns) { const struct stateVar * v; char * str; int tmplen; char buf[512]; tmplen = 512; str = (char *)malloc(tmplen); if(str == NULL) return NULL; *len = 0; v = s->serviceStateTable; snprintf(buf, sizeof(buf), "", servns); str = strcat_str(str, len, &tmplen, buf); while(v->name) { if(v->itype & EVENTED) { snprintf(buf, sizeof(buf), "<%s>", v->name); str = strcat_str(str, len, &tmplen, buf); //printf("", v->name); switch(v->ieventvalue) { case 0: break; case 255: /* Magical values should go around here */ if( strcmp(v->name, "SystemUpdateID") == 0 ) { snprintf(buf, sizeof(buf), "%d", updateID); str = strcat_str(str, len, &tmplen, buf); } break; default: str = strcat_str(str, len, &tmplen, upnpallowedvalues[v->ieventvalue]); //printf("%s", upnpallowedvalues[v->ieventvalue]); } snprintf(buf, sizeof(buf), "", v->name); str = strcat_str(str, len, &tmplen, buf); //printf("\n", v->name); } v++; } str = strcat_str(str, len, &tmplen, ""); //printf("\n"); //printf("\n"); //printf("%d\n", tmplen); str[*len] = '\0'; return str; } char * getVarsContentDirectory(int * l) { return genEventVars(l, &scpdContentDirectory, "urn:schemas-upnp-org:service:ContentDirectory:1"); } char * getVarsConnectionManager(int * l) { return genEventVars(l, &scpdConnectionManager, "urn:schemas-upnp-org:service:ConnectionManager:1"); } char * getVarsX_MS_MediaReceiverRegistrar(int * l) { return genEventVars(l, &scpdX_MS_MediaReceiverRegistrar, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"); } minidlna-1.1.5+dfsg/upnpdescgen.h000066400000000000000000000063611261774340000167260ustar00rootroot00000000000000/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * * Copyright (c) 2006-2008, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __UPNPDESCGEN_H__ #define __UPNPDESCGEN_H__ #include "config.h" /* for the root description * The child list reference is stored in "data" member using the * INITHELPER macro with index/nchild always in the * same order, whatever the endianness */ struct XMLElt { const char * eltname; /* begin with '/' if no child */ const char * data; /* Value */ }; /* for service description */ struct serviceDesc { const struct action * actionList; const struct stateVar * serviceStateTable; }; struct action { const char * name; const struct argument * args; }; struct argument { const char * name; /* the name of the argument */ unsigned char dir; /* 1 = in, 2 = out */ unsigned char relatedVar; /* index of the related variable */ }; #define EVENTED 1<<7 struct stateVar { const char * name; unsigned char itype; /* MSB: sendEvent flag, 7 LSB: index in upnptypes */ unsigned char idefault; /* default value */ unsigned char iallowedlist; /* index in allowed values list */ unsigned char ieventvalue; /* fixed value returned or magical values */ }; /* little endian * The code has now be tested on big endian architecture */ #define INITHELPER(i, n) ((char *)((n<<16)|i)) /* char * genRootDesc(int *); * returns: NULL on error, string allocated on the heap */ char * genRootDesc(int * len); char * genRootDescSamsung(int * len); /* for the two following functions */ char * genContentDirectory(int * len); char * genConnectionManager(int * len); char * genX_MS_MediaReceiverRegistrar(int * len); char * getVarsContentDirectory(int * len); char * getVarsConnectionManager(int * len); char * getVarsX_MS_MediaReceiverRegistrar(int * len); #endif minidlna-1.1.5+dfsg/upnpevents.c000066400000000000000000000323521261774340000166140ustar00rootroot00000000000000/* MiniDLNA project * http://minidlna.sourceforge.net/ * * MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . * * Portions of the code from the MiniUPnP project: * * Copyright (c) 2006-2007, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "upnpevents.h" #include "minidlnapath.h" #include "upnpglobalvars.h" #include "upnpdescgen.h" #include "uuid.h" #include "utils.h" #include "log.h" /* stuctures definitions */ struct subscriber { LIST_ENTRY(subscriber) entries; struct upnp_event_notify * notify; time_t timeout; uint32_t seq; enum subscriber_service_enum service; char uuid[42]; char callback[]; }; struct upnp_event_notify { LIST_ENTRY(upnp_event_notify) entries; int s; /* socket */ enum { ECreated=1, EConnecting, ESending, EWaitingForResponse, EFinished, EError } state; struct subscriber * sub; char * buffer; int buffersize; int tosend; int sent; const char * path; char addrstr[16]; char portstr[8]; }; /* prototypes */ static void upnp_event_create_notify(struct subscriber * sub); /* Subscriber list */ LIST_HEAD(listhead, subscriber) subscriberlist = { NULL }; /* notify list */ LIST_HEAD(listheadnotif, upnp_event_notify) notifylist = { NULL }; /* create a new subscriber */ static struct subscriber * newSubscriber(const char * eventurl, const char * callback, int callbacklen) { struct subscriber * tmp; if(!eventurl || !callback || !callbacklen) return NULL; tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1); if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0) tmp->service = EContentDirectory; else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0) tmp->service = EConnectionManager; else if(strcmp(eventurl, X_MS_MEDIARECEIVERREGISTRAR_EVENTURL)==0) tmp->service = EMSMediaReceiverRegistrar; else { free(tmp); return NULL; } memcpy(tmp->callback, callback, callbacklen); tmp->callback[callbacklen] = '\0'; /* make a dummy uuid */ strncpyt(tmp->uuid, uuidvalue, sizeof(tmp->uuid)); if( get_uuid_string(tmp->uuid+5) != 0 ) { tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff); } return tmp; } /* creates a new subscriber and adds it to the subscriber list * also initiate 1st notify */ const char * upnpevents_addSubscriber(const char * eventurl, const char * callback, int callbacklen, int timeout) { struct subscriber * tmp; DPRINTF(E_DEBUG, L_HTTP, "addSubscriber(%s, %.*s, %d)\n", eventurl, callbacklen, callback, timeout); tmp = newSubscriber(eventurl, callback, callbacklen); if(!tmp) return NULL; if(timeout) tmp->timeout = time(NULL) + timeout; LIST_INSERT_HEAD(&subscriberlist, tmp, entries); upnp_event_create_notify(tmp); return tmp->uuid; } /* renew a subscription (update the timeout) */ int renewSubscription(const char * sid, int sidlen, int timeout) { struct subscriber * sub; for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { if(memcmp(sid, sub->uuid, 41) == 0) { sub->timeout = (timeout ? time(NULL) + timeout : 0); return 0; } } return -1; } int upnpevents_removeSubscriber(const char * sid, int sidlen) { struct subscriber * sub; if(!sid) return -1; DPRINTF(E_DEBUG, L_HTTP, "removeSubscriber(%.*s)\n", sidlen, sid); for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { if(memcmp(sid, sub->uuid, 41) == 0) { if(sub->notify) { sub->notify->sub = NULL; } LIST_REMOVE(sub, entries); free(sub); return 0; } } return -1; } void upnpevents_removeSubscribers(void) { struct subscriber * sub; for(sub = subscriberlist.lh_first; sub != NULL; sub = subscriberlist.lh_first) { upnpevents_removeSubscriber(sub->uuid, sizeof(sub->uuid)); } } /* notifies all subscribers of a SystemUpdateID change */ void upnp_event_var_change_notify(enum subscriber_service_enum service) { struct subscriber * sub; for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { if(sub->service == service && sub->notify == NULL) upnp_event_create_notify(sub); } } /* create and add the notify object to the list */ static void upnp_event_create_notify(struct subscriber * sub) { struct upnp_event_notify * obj; int flags; obj = calloc(1, sizeof(struct upnp_event_notify)); if(!obj) { DPRINTF(E_ERROR, L_HTTP, "%s: calloc(): %s\n", "upnp_event_create_notify", strerror(errno)); return; } obj->sub = sub; obj->state = ECreated; obj->s = socket(PF_INET, SOCK_STREAM, 0); if(obj->s<0) { DPRINTF(E_ERROR, L_HTTP, "%s: socket(): %s\n", "upnp_event_create_notify", strerror(errno)); goto error; } if((flags = fcntl(obj->s, F_GETFL, 0)) < 0) { DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_GETFL..): %s\n", "upnp_event_create_notify", strerror(errno)); goto error; } if(fcntl(obj->s, F_SETFL, flags | O_NONBLOCK) < 0) { DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_SETFL..): %s\n", "upnp_event_create_notify", strerror(errno)); goto error; } if(sub) sub->notify = obj; LIST_INSERT_HEAD(¬ifylist, obj, entries); return; error: if(obj->s >= 0) close(obj->s); free(obj); } static void upnp_event_notify_connect(struct upnp_event_notify * obj) { int i; const char * p; unsigned short port; struct sockaddr_in addr; if(!obj) return; memset(&addr, 0, sizeof(addr)); i = 0; if(obj->sub == NULL) { obj->state = EError; return; } p = obj->sub->callback; p += 7; /* http:// */ while(*p != '/' && *p != ':' && i < (sizeof(obj->addrstr)-1)) obj->addrstr[i++] = *(p++); obj->addrstr[i] = '\0'; if(*p == ':') { obj->portstr[0] = *p; i = 1; p++; port = (unsigned short)atoi(p); while(*p != '/' && *p != '\0') { if(i<7) obj->portstr[i++] = *p; p++; } obj->portstr[i] = 0; } else { port = 80; obj->portstr[0] = '\0'; } if( *p ) obj->path = p; else obj->path = "/"; addr.sin_family = AF_INET; inet_aton(obj->addrstr, &addr.sin_addr); addr.sin_port = htons(port); DPRINTF(E_DEBUG, L_HTTP, "%s: '%s' %hu '%s'\n", "upnp_event_notify_connect", obj->addrstr, port, obj->path); obj->state = EConnecting; if(connect(obj->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { if(errno != EINPROGRESS && errno != EWOULDBLOCK) { DPRINTF(E_ERROR, L_HTTP, "%s: connect(): %s\n", "upnp_event_notify_connect", strerror(errno)); obj->state = EError; } } } static void upnp_event_prepare(struct upnp_event_notify * obj) { static const char notifymsg[] = "NOTIFY %s HTTP/1.1\r\n" "Host: %s%s\r\n" "Content-Type: text/xml; charset=\"utf-8\"\r\n" "Content-Length: %d\r\n" "NT: upnp:event\r\n" "NTS: upnp:propchange\r\n" "SID: %s\r\n" "SEQ: %u\r\n" "Connection: close\r\n" "Cache-Control: no-cache\r\n" "\r\n" "%.*s\r\n"; char * xml; int l; if(obj->sub == NULL) { obj->state = EError; return; } switch(obj->sub->service) { case EContentDirectory: xml = getVarsContentDirectory(&l); break; case EConnectionManager: xml = getVarsConnectionManager(&l); break; case EMSMediaReceiverRegistrar: xml = getVarsX_MS_MediaReceiverRegistrar(&l); break; default: xml = NULL; l = 0; } obj->tosend = asprintf(&(obj->buffer), notifymsg, obj->path, obj->addrstr, obj->portstr, l+2, obj->sub->uuid, obj->sub->seq, l, xml); obj->buffersize = obj->tosend; free(xml); DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event response:\n%s\n", obj->buffer); obj->state = ESending; } static void upnp_event_send(struct upnp_event_notify * obj) { int i; //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event:\n%s", obj->buffer+obj->sent); while( obj->sent < obj->tosend ) { i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0); if(i<0) { DPRINTF(E_WARN, L_HTTP, "%s: send(): %s\n", "upnp_event_send", strerror(errno)); obj->state = EError; return; } obj->sent += i; } if(obj->sent == obj->tosend) obj->state = EWaitingForResponse; } static void upnp_event_recv(struct upnp_event_notify * obj) { int n; n = recv(obj->s, obj->buffer, obj->buffersize, 0); if(n<0) { DPRINTF(E_ERROR, L_HTTP, "%s: recv(): %s\n", "upnp_event_recv", strerror(errno)); obj->state = EError; return; } DPRINTF(E_DEBUG, L_HTTP, "%s: (%dbytes) %.*s\n", "upnp_event_recv", n, n, obj->buffer); obj->state = EFinished; if(obj->sub) { obj->sub->seq++; if (!obj->sub->seq) obj->sub->seq++; } } static void upnp_event_process_notify(struct upnp_event_notify * obj) { switch(obj->state) { case EConnecting: /* now connected or failed to connect */ upnp_event_prepare(obj); upnp_event_send(obj); break; case ESending: upnp_event_send(obj); break; case EWaitingForResponse: upnp_event_recv(obj); break; case EFinished: close(obj->s); obj->s = -1; break; default: DPRINTF(E_ERROR, L_HTTP, "upnp_event_process_notify: unknown state\n"); } } void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd) { struct upnp_event_notify * obj; for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { DPRINTF(E_DEBUG, L_HTTP, "upnpevents_selectfds: %p %d %d\n", obj, obj->state, obj->s); if(obj->s >= 0) { switch(obj->state) { case ECreated: upnp_event_notify_connect(obj); if(obj->state != EConnecting) break; case EConnecting: case ESending: FD_SET(obj->s, writeset); if(obj->s > *max_fd) *max_fd = obj->s; break; case EWaitingForResponse: FD_SET(obj->s, readset); if(obj->s > *max_fd) *max_fd = obj->s; break; default: break; } } } } void upnpevents_processfds(fd_set *readset, fd_set *writeset) { struct upnp_event_notify * obj; struct upnp_event_notify * next; struct subscriber * sub; struct subscriber * subnext; time_t curtime; for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { DPRINTF(E_DEBUG, L_HTTP, "%s: %p %d %d %d %d\n", "upnpevents_processfds", obj, obj->state, obj->s, FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset)); if(obj->s >= 0) { if(FD_ISSET(obj->s, readset) || FD_ISSET(obj->s, writeset)) upnp_event_process_notify(obj); } } obj = notifylist.lh_first; while(obj != NULL) { next = obj->entries.le_next; if(obj->state == EError || obj->state == EFinished) { if(obj->s >= 0) { close(obj->s); } if(obj->sub) obj->sub->notify = NULL; #if 0 /* Just let it time out instead of explicitly removing the subscriber */ /* remove also the subscriber from the list if there was an error */ if(obj->state == EError && obj->sub) { LIST_REMOVE(obj->sub, entries); free(obj->sub); } #endif free(obj->buffer); LIST_REMOVE(obj, entries); free(obj); } obj = next; } /* remove timeouted subscribers */ curtime = time(NULL); for(sub = subscriberlist.lh_first; sub != NULL; ) { subnext = sub->entries.le_next; if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) { LIST_REMOVE(sub, entries); free(sub); } sub = subnext; } } minidlna-1.1.5+dfsg/upnpevents.h000066400000000000000000000060571261774340000166240ustar00rootroot00000000000000/* MiniDLNA project * http://minidlna.sourceforge.net/ * * MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . * * Portions of the code from the MiniUPnP project: * * Copyright (c) 2006-2007, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __UPNPEVENTS_H__ #define __UPNPEVENTS_H__ enum subscriber_service_enum { EContentDirectory = 1, EConnectionManager, EMSMediaReceiverRegistrar }; void upnp_event_var_change_notify(enum subscriber_service_enum service); const char * upnpevents_addSubscriber(const char * eventurl, const char * callback, int callbacklen, int timeout); int upnpevents_removeSubscriber(const char * sid, int sidlen); void upnpevents_removeSubscribers(void); int renewSubscription(const char * sid, int sidlen, int timeout); void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd); void upnpevents_processfds(fd_set *readset, fd_set *writeset); #ifdef USE_MINIUPNPDCTL void write_events_details(int s); #endif #endif minidlna-1.1.5+dfsg/upnpglobalvars.c000066400000000000000000000072141261774340000174430ustar00rootroot00000000000000/* MiniDLNA project * * http://sourceforge.net/projects/minidlna/ * * MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . * * Portions of the code from the MiniUPnP project: * * Copyright (c) 2006-2007, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include "config.h" #include "upnpglobalvars.h" /* startup time */ time_t startup_time = 0; struct runtime_vars_s runtime_vars; uint32_t runtime_flags = INOTIFY_MASK; const char *pidfilename = "/var/run/minidlna/minidlna.pid"; char uuidvalue[] = "uuid:00000000-0000-0000-0000-000000000000"; char modelname[MODELNAME_MAX_LEN] = ROOTDEV_MODELNAME; char modelnumber[MODELNUMBER_MAX_LEN] = MINIDLNA_VERSION; char serialnumber[SERIALNUMBER_MAX_LEN] = "00000000"; #if PNPX char pnpx_hwid[] = "VEN_0000&DEV_0000&REV_01 VEN_0033&DEV_0001&REV_01"; #endif /* presentation url : * http://nnn.nnn.nnn.nnn:ppppp/ => max 30 bytes including terminating 0 */ char presentationurl[PRESENTATIONURL_MAX_LEN]; int n_lan_addr = 0; struct lan_addr_s lan_addr[MAX_LAN_ADDR]; int sssdp = -1; /* Path of the Unix socket used to communicate with MiniSSDPd */ const char * minissdpdsocketpath = "/var/run/minissdpd.sock"; /* UPnP-A/V [DLNA] */ sqlite3 *db; char friendly_name[FRIENDLYNAME_MAX_LEN]; char db_path[PATH_MAX] = {'\0'}; char log_path[PATH_MAX] = {'\0'}; struct media_dir_s * media_dirs = NULL; struct album_art_name_s * album_art_names = NULL; short int scanning = 0; volatile short int quitting = 0; volatile uint32_t updateID = 0; const char *force_sort_criteria = NULL; minidlna-1.1.5+dfsg/upnpglobalvars.h000066400000000000000000000227431261774340000174540ustar00rootroot00000000000000/* MiniDLNA project * * http://sourceforge.net/projects/minidlna/ * * MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . * * Portions of the code from the MiniUPnP project: * * Copyright (c) 2006-2007, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __UPNPGLOBALVARS_H__ #define __UPNPGLOBALVARS_H__ #include #include "minidlnatypes.h" #include "clients.h" #include "config.h" #include #define MINIDLNA_VERSION "1.1.5" #ifdef NETGEAR # define SERVER_NAME "ReadyDLNA" #else # define SERVER_NAME "MiniDLNA" #endif #define USE_FORK 1 #define DB_VERSION 9 #ifdef ENABLE_NLS #define _(string) gettext(string) #else #define _(string) (string) #endif #define THISORNUL(s) (s ? s : "") #ifndef PNPX #define PNPX 0 #endif #define RESOURCE_PROTOCOL_INFO_VALUES \ "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN," \ "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM," \ "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED," \ "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG," \ "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO," \ "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_60_AC3_ISO," \ "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HP_HD_AC3_ISO," \ "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO," \ "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AC3_ISO," \ "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO," \ "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO," \ "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AC3_ISO," \ "http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO," \ "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC," \ "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL," \ "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_HD_NA_ISO," \ "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO," \ "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO," \ "http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1," \ "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5," \ "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3," \ "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_BL_CIF15_AAC_520," \ "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_BL_CIF30_AAC_940," \ "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_BL_L31_HD_AAC," \ "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_BL_L32_HD_AAC," \ "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_BL_L3L_SD_AAC," \ "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC," \ "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC," \ "http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC," \ "http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC," \ "http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_VGA_AAC," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_60_AC3," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_60_AC3_T," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HP_HD_AC3_T," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AC3," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AC3_T," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AC3," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AC3_T," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_HD_NA," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_HD_NA_T," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA," \ "http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T," \ "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPLL_BASE," \ "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPML_BASE," \ "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPML_MP3," \ "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE," \ "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL," \ "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO," \ "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL," \ "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO," \ "http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC," \ "http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR," \ "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3," \ "http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE," \ "http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL," \ "http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAPRO," \ "http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMALSL," \ "http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMALSL_MULT5," \ "http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320," \ "http-get:*:audio/3gpp:DLNA.ORG_PN=AAC_ISO_320," \ "http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO," \ "http-get:*:audio/mp4:DLNA.ORG_PN=AAC_MULT5_ISO," \ "http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM," \ "http-get:*:image/jpeg:*," \ "http-get:*:video/avi:*," \ "http-get:*:video/divx:*," \ "http-get:*:video/x-matroska:*," \ "http-get:*:video/mpeg:*," \ "http-get:*:video/mp4:*," \ "http-get:*:video/x-ms-wmv:*," \ "http-get:*:video/x-msvideo:*," \ "http-get:*:video/x-flv:*," \ "http-get:*:video/x-tivo-mpeg:*," \ "http-get:*:video/quicktime:*," \ "http-get:*:audio/mp4:*," \ "http-get:*:audio/x-wav:*," \ "http-get:*:audio/x-flac:*," \ "http-get:*:application/ogg:*" #define DLNA_FLAG_DLNA_V1_5 0x00100000 #define DLNA_FLAG_HTTP_STALLING 0x00200000 #define DLNA_FLAG_TM_B 0x00400000 #define DLNA_FLAG_TM_I 0x00800000 #define DLNA_FLAG_TM_S 0x01000000 #define DLNA_FLAG_LOP_BYTES 0x20000000 #define DLNA_FLAG_LOP_NPT 0x40000000 /* startup time */ extern time_t startup_time; extern struct runtime_vars_s runtime_vars; /* runtime boolean flags */ extern uint32_t runtime_flags; #define INOTIFY_MASK 0x0001 #define TIVO_MASK 0x0002 #define DLNA_STRICT_MASK 0x0004 #define NO_PLAYLIST_MASK 0x0008 #define SYSTEMD_MASK 0x0010 #define MERGE_MEDIA_DIRS_MASK 0x0020 #define SETFLAG(mask) runtime_flags |= mask #define GETFLAG(mask) (runtime_flags & mask) #define CLEARFLAG(mask) runtime_flags &= ~mask extern const char *pidfilename; extern char uuidvalue[]; #define MODELNAME_MAX_LEN 64 extern char modelname[]; #define MODELNUMBER_MAX_LEN 16 extern char modelnumber[]; #define SERIALNUMBER_MAX_LEN 16 extern char serialnumber[]; #define PRESENTATIONURL_MAX_LEN 64 extern char presentationurl[]; #if PNPX extern char pnpx_hwid[]; #endif /* lan addresses */ extern int n_lan_addr; extern struct lan_addr_s lan_addr[]; extern int sssdp; extern const char *minissdpdsocketpath; /* UPnP-A/V [DLNA] */ extern sqlite3 *db; #define FRIENDLYNAME_MAX_LEN 64 extern char friendly_name[]; extern char db_path[]; extern char log_path[]; extern struct media_dir_s *media_dirs; extern struct album_art_name_s *album_art_names; extern short int scanning; extern volatile short int quitting; extern volatile uint32_t updateID; extern const char *force_sort_criteria; #endif minidlna-1.1.5+dfsg/upnphttp.c000066400000000000000000001440611261774340000162700ustar00rootroot00000000000000/* MiniDLNA project * * http://sourceforge.net/projects/minidlna/ * * MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . * * Portions of the code from the MiniUPnP project: * * Copyright (c) 2006-2007, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "upnpglobalvars.h" #include "upnphttp.h" #include "upnpdescgen.h" #include "minidlnapath.h" #include "upnpsoap.h" #include "upnpevents.h" #include "utils.h" #include "getifaddr.h" #include "image_utils.h" #include "log.h" #include "sql.h" #include #include "tivo_utils.h" #include "tivo_commands.h" #include "clients.h" #include "process.h" #include "sendfile.h" #define MAX_BUFFER_SIZE 2147483647 #define MIN_BUFFER_SIZE 65536 #define INIT_STR(s, d) { s.data = d; s.size = sizeof(d); s.off = 0; } #include "icons.c" enum event_type { E_INVALID, E_SUBSCRIBE, E_RENEW }; static void SendResp_icon(struct upnphttp *, char * url); static void SendResp_albumArt(struct upnphttp *, char * url); static void SendResp_caption(struct upnphttp *, char * url); static void SendResp_resizedimg(struct upnphttp *, char * url); static void SendResp_thumbnail(struct upnphttp *, char * url); static void SendResp_dlnafile(struct upnphttp *, char * url); struct upnphttp * New_upnphttp(int s) { struct upnphttp * ret; if(s<0) return NULL; ret = (struct upnphttp *)malloc(sizeof(struct upnphttp)); if(ret == NULL) return NULL; memset(ret, 0, sizeof(struct upnphttp)); ret->socket = s; return ret; } void CloseSocket_upnphttp(struct upnphttp * h) { if(close(h->socket) < 0) { DPRINTF(E_ERROR, L_HTTP, "CloseSocket_upnphttp: close(%d): %s\n", h->socket, strerror(errno)); } h->socket = -1; h->state = 100; } void Delete_upnphttp(struct upnphttp * h) { if(h) { if(h->socket >= 0) CloseSocket_upnphttp(h); free(h->req_buf); free(h->res_buf); free(h); } } /* parse HttpHeaders of the REQUEST */ static void ParseHttpHeaders(struct upnphttp * h) { int client = 0; char * line; char * colon; char * p; int n; line = h->req_buf; /* TODO : check if req_buf, contentoff are ok */ while(line < (h->req_buf + h->req_contentoff)) { colon = strchr(line, ':'); if(colon) { if(strncasecmp(line, "Content-Length", 14)==0) { p = colon; while(*p && (*p < '0' || *p > '9')) p++; h->req_contentlen = atoi(p); if(h->req_contentlen < 0) { DPRINTF(E_WARN, L_HTTP, "Invalid Content-Length %d", h->req_contentlen); h->req_contentlen = 0; } } else if(strncasecmp(line, "SOAPAction", 10)==0) { p = colon; n = 0; while(*p == ':' || *p == ' ' || *p == '\t') p++; while(p[n] >= ' ') n++; if(n >= 2 && ((p[0] == '"' && p[n-1] == '"') || (p[0] == '\'' && p[n-1] == '\''))) { p++; n -= 2; } h->req_soapAction = p; h->req_soapActionLen = n; } else if(strncasecmp(line, "Callback", 8)==0) { p = colon; while(*p && *p != '<' && *p != '\r' ) p++; n = 0; while(p[n] && p[n] != '>' && p[n] != '\r' ) n++; h->req_Callback = p + 1; h->req_CallbackLen = MAX(0, n - 1); } else if(strncasecmp(line, "SID", 3)==0) { //zqiu: fix bug for test 4.0.5 //Skip extra headers like "SIDHEADER: xxxxxx xxx" for(p=line+3;preq_SID = p; h->req_SIDLen = n; } } else if(strncasecmp(line, "NT", 2)==0) { p = colon + 1; while(isspace(*p)) p++; n = 0; while(p[n] && !isspace(p[n])) n++; h->req_NT = p; h->req_NTLen = n; } /* Timeout: Seconds-nnnn */ /* TIMEOUT Recommended. Requested duration until subscription expires, either number of seconds or infinite. Recommendation by a UPnP Forum working committee. Defined by UPnP vendor. Consists of the keyword "Second-" followed (without an intervening space) by either an integer or the keyword "infinite". */ else if(strncasecmp(line, "Timeout", 7)==0) { p = colon + 1; while(isspace(*p)) p++; if(strncasecmp(p, "Second-", 7)==0) { h->req_Timeout = atoi(p+7); } } // Range: bytes=xxx-yyy else if(strncasecmp(line, "Range", 5)==0) { p = colon + 1; while(isspace(*p)) p++; if(strncasecmp(p, "bytes=", 6)==0) { h->reqflags |= FLAG_RANGE; h->req_RangeStart = strtoll(p+6, &colon, 10); h->req_RangeEnd = colon ? atoll(colon+1) : 0; DPRINTF(E_DEBUG, L_HTTP, "Range Start-End: %lld - %lld\n", (long long)h->req_RangeStart, h->req_RangeEnd ? (long long)h->req_RangeEnd : -1); } } else if(strncasecmp(line, "Host", 4)==0) { int i; h->reqflags |= FLAG_HOST; p = colon + 1; while(isspace(*p)) p++; for(n = 0; niface = n; break; } } } else if(strncasecmp(line, "User-Agent", 10)==0) { int i; /* Skip client detection if we already detected it. */ if( client ) goto next_header; p = colon + 1; while(isspace(*p)) p++; for (i = 0; client_types[i].name; i++) { if (client_types[i].match_type != EUserAgent) continue; if (strstrc(p, client_types[i].match, '\r') != NULL) { client = i; break; } } } else if(strncasecmp(line, "X-AV-Client-Info", 16)==0) { int i; /* Skip client detection if we already detected it. */ if( client && client_types[client].type < EStandardDLNA150 ) goto next_header; p = colon + 1; while(isspace(*p)) p++; for (i = 0; client_types[i].name; i++) { if (client_types[i].match_type != EXAVClientInfo) continue; if (strstrc(p, client_types[i].match, '\r') != NULL) { client = i; break; } } } else if(strncasecmp(line, "Transfer-Encoding", 17)==0) { p = colon + 1; while(isspace(*p)) p++; if(strncasecmp(p, "chunked", 7)==0) { h->reqflags |= FLAG_CHUNKED; } } else if(strncasecmp(line, "Accept-Language", 15)==0) { h->reqflags |= FLAG_LANGUAGE; } else if(strncasecmp(line, "getcontentFeatures.dlna.org", 27)==0) { p = colon + 1; while(isspace(*p)) p++; if( (*p != '1') || !isspace(p[1]) ) h->reqflags |= FLAG_INVALID_REQ; } else if(strncasecmp(line, "TimeSeekRange.dlna.org", 22)==0) { h->reqflags |= FLAG_TIMESEEK; } else if(strncasecmp(line, "PlaySpeed.dlna.org", 18)==0) { h->reqflags |= FLAG_PLAYSPEED; } else if(strncasecmp(line, "realTimeInfo.dlna.org", 21)==0) { h->reqflags |= FLAG_REALTIMEINFO; } else if(strncasecmp(line, "getAvailableSeekRange.dlna.org", 21)==0) { p = colon + 1; while(isspace(*p)) p++; if( (*p != '1') || !isspace(p[1]) ) h->reqflags |= FLAG_INVALID_REQ; } else if(strncasecmp(line, "transferMode.dlna.org", 21)==0) { p = colon + 1; while(isspace(*p)) p++; if(strncasecmp(p, "Streaming", 9)==0) { h->reqflags |= FLAG_XFERSTREAMING; } if(strncasecmp(p, "Interactive", 11)==0) { h->reqflags |= FLAG_XFERINTERACTIVE; } if(strncasecmp(p, "Background", 10)==0) { h->reqflags |= FLAG_XFERBACKGROUND; } } else if(strncasecmp(line, "getCaptionInfo.sec", 18)==0) { h->reqflags |= FLAG_CAPTION; } else if(strncasecmp(line, "FriendlyName", 12)==0) { int i; p = colon + 1; while(isspace(*p)) p++; for (i = 0; client_types[i].name; i++) { if (client_types[i].match_type != EFriendlyName) continue; if (strstrc(p, client_types[i].match, '\r') != NULL) { client = i; break; } } } else if(strncasecmp(line, "uctt.upnp.org:", 14)==0) { /* Conformance testing */ SETFLAG(DLNA_STRICT_MASK); } } next_header: line = strstr(line, "\r\n"); if (!line) return; line += 2; } if( h->reqflags & FLAG_CHUNKED ) { char *endptr; h->req_chunklen = -1; if( h->req_buflen <= h->req_contentoff ) return; while( (line < (h->req_buf + h->req_buflen)) && (h->req_chunklen = strtol(line, &endptr, 16)) && (endptr != line) ) { endptr = strstr(endptr, "\r\n"); if (!endptr) { return; } line = endptr+h->req_chunklen+2; } if( endptr == line ) { h->req_chunklen = -1; return; } } /* If the client type wasn't found, search the cache. * This is done because a lot of clients like to send a * different User-Agent with different types of requests. */ h->req_client = SearchClientCache(h->clientaddr, 0); /* Add this client to the cache if it's not there already. */ if (!h->req_client) { h->req_client = AddClientCache(h->clientaddr, client); } else if (client) { enum client_types type = client_types[client].type; enum client_types ctype = h->req_client->type->type; /* If we know the client and our new detection is generic, use our cached info */ /* If we detected a Samsung Series B earlier, don't overwrite it with Series A info */ if ((ctype && ctype < EStandardDLNA150 && type >= EStandardDLNA150) || (ctype == ESamsungSeriesB && type == ESamsungSeriesA)) return; h->req_client->type = &client_types[client]; h->req_client->age = time(NULL); } } /* very minimalistic 400 error message */ static void Send400(struct upnphttp * h) { static const char body400[] = "400 Bad Request" "

Bad Request

The request is invalid" " for this HTTP version.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 400, "Bad Request", body400, sizeof(body400) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } /* very minimalistic 404 error message */ static void Send404(struct upnphttp * h) { static const char body404[] = "404 Not Found" "

Not Found

The requested URL was not found" " on this server.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 404, "Not Found", body404, sizeof(body404) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } /* very minimalistic 406 error message */ static void Send406(struct upnphttp * h) { static const char body406[] = "406 Not Acceptable" "

Not Acceptable

An unsupported operation" " was requested.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 406, "Not Acceptable", body406, sizeof(body406) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } /* very minimalistic 416 error message */ static void Send416(struct upnphttp * h) { static const char body416[] = "416 Requested Range Not Satisfiable" "

Requested Range Not Satisfiable

The requested range" " was outside the file's size.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 416, "Requested Range Not Satisfiable", body416, sizeof(body416) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } /* very minimalistic 500 error message */ void Send500(struct upnphttp * h) { static const char body500[] = "500 Internal Server Error" "

Internal Server Error

Server encountered " "and Internal Error.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 500, "Internal Server Errror", body500, sizeof(body500) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } /* very minimalistic 501 error message */ void Send501(struct upnphttp * h) { static const char body501[] = "501 Not Implemented" "

Not Implemented

The HTTP Method " "is not implemented by this server.\r\n"; h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 501, "Not Implemented", body501, sizeof(body501) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } /* Sends the description generated by the parameter */ static void sendXMLdesc(struct upnphttp * h, char * (f)(int *)) { char * desc; int len; desc = f(&len); if(!desc) { DPRINTF(E_ERROR, L_HTTP, "Failed to generate XML description\n"); Send500(h); return; } BuildResp_upnphttp(h, desc, len); SendResp_upnphttp(h); CloseSocket_upnphttp(h); free(desc); } #ifdef READYNAS static void SendResp_readynas_admin(struct upnphttp * h) { char body[128]; int l; h->respflags = FLAG_HTML; l = snprintf(body, sizeof(body), "", lan_addr[h->iface].str); BuildResp_upnphttp(h, body, l); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } #endif static void SendResp_presentation(struct upnphttp * h) { struct string_s str; char body[4096]; int a, v, p, i; INIT_STR(str, body); h->respflags = FLAG_HTML; a = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'a*'"); v = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'v*'"); p = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'i*'"); strcatf(&str, "" SERVER_NAME " " MINIDLNA_VERSION "" "
" "

" SERVER_NAME " status

"); strcatf(&str, "

Media library

" "" "" "" "" "
Audio files%d
Video files%d
Image files%d
", a, v, p); if (scanning) strcatf(&str, "
* Media scan in progress
"); strcatf(&str, "

Connected clients

" "" ""); for (i = 0; i < CLIENT_CACHE_SLOTS; i++) { if (!clients[i].addr.s_addr) continue; strcatf(&str, "", i, clients[i].type->name, inet_ntoa(clients[i].addr), clients[i].mac[0], clients[i].mac[1], clients[i].mac[2], clients[i].mac[3], clients[i].mac[4], clients[i].mac[5], clients[i].connections); } strcatf(&str, "
IDTypeIP AddressHW AddressConnections
%d%s%s%02X:%02X:%02X:%02X:%02X:%02X%d
"); strcatf(&str, "
%d connection%s currently open
", number_of_children, (number_of_children == 1 ? "" : "s")); strcatf(&str, "\r\n"); BuildResp_upnphttp(h, str.data, str.off); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } /* ProcessHTTPPOST_upnphttp() * executes the SOAP query if it is possible */ static void ProcessHTTPPOST_upnphttp(struct upnphttp * h) { if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) { if(h->req_soapAction) { /* we can process the request */ DPRINTF(E_DEBUG, L_HTTP, "SOAPAction: %.*s\n", h->req_soapActionLen, h->req_soapAction); ExecuteSoapAction(h, h->req_soapAction, h->req_soapActionLen); } else { static const char err400str[] = "Bad request"; DPRINTF(E_WARN, L_HTTP, "No SOAPAction in HTTP headers\n"); h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 400, "Bad Request", err400str, sizeof(err400str) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } } else { /* waiting for remaining data */ h->state = 1; } } static int check_event(struct upnphttp *h) { enum event_type type; if (h->req_Callback) { if (h->req_SID || !h->req_NT) { BuildResp2_upnphttp(h, 400, "Bad Request", "Bad request", 37); type = E_INVALID; } else if (strncmp(h->req_Callback, "http://", 7) != 0 || strncmp(h->req_NT, "upnp:event", h->req_NTLen) != 0) { /* Missing or invalid CALLBACK : 412 Precondition Failed. * If CALLBACK header is missing or does not contain a valid HTTP URL, * the publisher must respond with HTTP error 412 Precondition Failed*/ BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); type = E_INVALID; } else type = E_SUBSCRIBE; } else if (h->req_SID) { /* subscription renew */ if (h->req_NT) { BuildResp2_upnphttp(h, 400, "Bad Request", "Bad request", 37); type = E_INVALID; } else type = E_RENEW; } else { BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); type = E_INVALID; } return type; } static void ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path) { const char * sid; enum event_type type; DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPSubscribe %s\n", path); DPRINTF(E_DEBUG, L_HTTP, "Callback '%.*s' Timeout=%d\n", h->req_CallbackLen, h->req_Callback, h->req_Timeout); DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID); type = check_event(h); if (type == E_SUBSCRIBE) { /* - add to the subscriber list * - respond HTTP/x.x 200 OK * - Send the initial event message */ /* Server:, SID:; Timeout: Second-(xx|infinite) */ sid = upnpevents_addSubscriber(path, h->req_Callback, h->req_CallbackLen, h->req_Timeout); h->respflags = FLAG_TIMEOUT; if (sid) { DPRINTF(E_DEBUG, L_HTTP, "generated sid=%s\n", sid); h->respflags |= FLAG_SID; h->req_SID = sid; h->req_SIDLen = strlen(sid); } BuildResp_upnphttp(h, 0, 0); } else if (type == E_RENEW) { /* subscription renew */ if (renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0) { /* Invalid SID 412 Precondition Failed. If a SID does not correspond to a known, un-expired subscription, the publisher must respond with HTTP error 412 Precondition Failed. */ BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); } else { /* A DLNA device must enforce a 5 minute timeout */ h->respflags = FLAG_TIMEOUT; h->req_Timeout = 300; h->respflags |= FLAG_SID; BuildResp_upnphttp(h, 0, 0); } } SendResp_upnphttp(h); CloseSocket_upnphttp(h); } static void ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path) { enum event_type type; DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPUnSubscribe %s\n", path); DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID); /* Remove from the list */ type = check_event(h); if (type != E_INVALID) { if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0) BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); else BuildResp_upnphttp(h, 0, 0); } SendResp_upnphttp(h); CloseSocket_upnphttp(h); } /* Parse and process Http Query * called once all the HTTP headers have been received. */ static void ProcessHttpQuery_upnphttp(struct upnphttp * h) { char HttpCommand[16]; char HttpUrl[512]; char * HttpVer; char * p; int i; p = h->req_buf; if(!p) return; for(i = 0; i<15 && *p && *p != ' ' && *p != '\r'; i++) HttpCommand[i] = *(p++); HttpCommand[i] = '\0'; while(*p==' ') p++; if(strncmp(p, "http://", 7) == 0) { p = p+7; while(*p!='/') p++; } for(i = 0; i<511 && *p && *p != ' ' && *p != '\r'; i++) HttpUrl[i] = *(p++); HttpUrl[i] = '\0'; while(*p==' ') p++; HttpVer = h->HttpVer; for(i = 0; i<15 && *p && *p != '\r'; i++) HttpVer[i] = *(p++); HttpVer[i] = '\0'; /* set the interface here initially, in case there is no Host header */ for(i = 0; iclientaddr.s_addr & lan_addr[i].mask.s_addr) == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr)) { h->iface = i; break; } } ParseHttpHeaders(h); /* see if we need to wait for remaining data */ if( (h->reqflags & FLAG_CHUNKED) ) { if( h->req_chunklen == -1) { Send400(h); return; } if( h->req_chunklen ) { h->state = 2; return; } char *chunkstart, *chunk, *endptr, *endbuf; chunk = endbuf = chunkstart = h->req_buf + h->req_contentoff; while( (h->req_chunklen = strtol(chunk, &endptr, 16)) && (endptr != chunk) ) { endptr = strstr(endptr, "\r\n"); if (!endptr) { Send400(h); return; } endptr += 2; memmove(endbuf, endptr, h->req_chunklen); endbuf += h->req_chunklen; chunk = endptr + h->req_chunklen; } h->req_contentlen = endbuf - chunkstart; h->req_buflen = endbuf - h->req_buf; h->state = 100; } DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf); if(strcmp("POST", HttpCommand) == 0) { h->req_command = EPost; ProcessHTTPPOST_upnphttp(h); } else if((strcmp("GET", HttpCommand) == 0) || (strcmp("HEAD", HttpCommand) == 0)) { if( ((strcmp(h->HttpVer, "HTTP/1.1")==0) && !(h->reqflags & FLAG_HOST)) || (h->reqflags & FLAG_INVALID_REQ) ) { DPRINTF(E_WARN, L_HTTP, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n"); Send400(h); return; } /* 7.3.33.4 */ else if( (h->reqflags & (FLAG_TIMESEEK|FLAG_PLAYSPEED)) && !(h->reqflags & FLAG_RANGE) ) { DPRINTF(E_WARN, L_HTTP, "DLNA %s requested, responding ERROR 406\n", h->reqflags&FLAG_TIMESEEK ? "TimeSeek" : "PlaySpeed"); Send406(h); return; } else if(strcmp("GET", HttpCommand) == 0) { h->req_command = EGet; } else { h->req_command = EHead; } if(strcmp(ROOTDESC_PATH, HttpUrl) == 0) { /* If it's a Xbox360, we might need a special friendly_name to be recognized */ if( h->req_client && h->req_client->type->type == EXbox ) { char model_sav[2]; i = 0; memcpy(model_sav, modelnumber, 2); strcpy(modelnumber, "1"); if( !strchr(friendly_name, ':') ) { i = strlen(friendly_name); snprintf(friendly_name+i, FRIENDLYNAME_MAX_LEN-i, ": 1"); } sendXMLdesc(h, genRootDesc); if( i ) friendly_name[i] = '\0'; memcpy(modelnumber, model_sav, 2); } else if( h->req_client && h->req_client->type->flags & FLAG_SAMSUNG_DCM10 ) { sendXMLdesc(h, genRootDescSamsung); } else { sendXMLdesc(h, genRootDesc); } } else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0) { sendXMLdesc(h, genContentDirectory); } else if(strcmp(CONNECTIONMGR_PATH, HttpUrl) == 0) { sendXMLdesc(h, genConnectionManager); } else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH, HttpUrl) == 0) { sendXMLdesc(h, genX_MS_MediaReceiverRegistrar); } else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0) { SendResp_dlnafile(h, HttpUrl+12); } else if(strncmp(HttpUrl, "/Thumbnails/", 12) == 0) { SendResp_thumbnail(h, HttpUrl+12); } else if(strncmp(HttpUrl, "/AlbumArt/", 10) == 0) { SendResp_albumArt(h, HttpUrl+10); } #ifdef TIVO_SUPPORT else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0) { if( GETFLAG(TIVO_MASK) ) { if( *(HttpUrl+12) == '?' ) { ProcessTiVoCommand(h, HttpUrl+13); } else { DPRINTF(E_WARN, L_HTTP, "Invalid TiVo request! %s\n", HttpUrl+12); Send404(h); } } else { DPRINTF(E_WARN, L_HTTP, "TiVo request with out TiVo support enabled! %s\n", HttpUrl+12); Send404(h); } } #endif else if(strncmp(HttpUrl, "/Resized/", 9) == 0) { SendResp_resizedimg(h, HttpUrl+9); } else if(strncmp(HttpUrl, "/icons/", 7) == 0) { SendResp_icon(h, HttpUrl+7); } else if(strncmp(HttpUrl, "/Captions/", 10) == 0) { SendResp_caption(h, HttpUrl+10); } else if(strncmp(HttpUrl, "/status", 7) == 0) { SendResp_presentation(h); } else if(strcmp(HttpUrl, "/") == 0) { #ifdef READYNAS SendResp_readynas_admin(h); #else SendResp_presentation(h); #endif } else { DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", HttpUrl); Send404(h); } } else if(strcmp("SUBSCRIBE", HttpCommand) == 0) { h->req_command = ESubscribe; ProcessHTTPSubscribe_upnphttp(h, HttpUrl); } else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0) { h->req_command = EUnSubscribe; ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl); } else { DPRINTF(E_WARN, L_HTTP, "Unsupported HTTP Command %s\n", HttpCommand); Send501(h); } } void Process_upnphttp(struct upnphttp * h) { char buf[2048]; int n; if(!h) return; switch(h->state) { case 0: n = recv(h->socket, buf, 2048, 0); if(n<0) { DPRINTF(E_ERROR, L_HTTP, "recv (state0): %s\n", strerror(errno)); h->state = 100; } else if(n==0) { DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n"); h->state = 100; } else { int new_req_buflen; const char * endheaders; /* if 1st arg of realloc() is null, * realloc behaves the same as malloc() */ new_req_buflen = n + h->req_buflen + 1; if (new_req_buflen >= 1024 * 1024) { DPRINTF(E_ERROR, L_HTTP, "Receive headers too large (received %d bytes)\n", new_req_buflen); h->state = 100; break; } h->req_buf = (char *)realloc(h->req_buf, new_req_buflen); if (!h->req_buf) { DPRINTF(E_ERROR, L_HTTP, "Receive headers: %s\n", strerror(errno)); h->state = 100; break; } memcpy(h->req_buf + h->req_buflen, buf, n); h->req_buflen += n; h->req_buf[h->req_buflen] = '\0'; /* search for the string "\r\n\r\n" */ endheaders = strstr(h->req_buf, "\r\n\r\n"); if(endheaders) { h->req_contentoff = endheaders - h->req_buf + 4; h->req_contentlen = h->req_buflen - h->req_contentoff; ProcessHttpQuery_upnphttp(h); } } break; case 1: case 2: n = recv(h->socket, buf, sizeof(buf), 0); if(n < 0) { DPRINTF(E_ERROR, L_HTTP, "recv (state%d): %s\n", h->state, strerror(errno)); h->state = 100; } else if(n == 0) { DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n"); h->state = 100; } else { buf[sizeof(buf)-1] = '\0'; /*fwrite(buf, 1, n, stdout);*/ /* debug */ h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen); if (!h->req_buf) { DPRINTF(E_ERROR, L_HTTP, "Receive request body: %s\n", strerror(errno)); h->state = 100; break; } memcpy(h->req_buf + h->req_buflen, buf, n); h->req_buflen += n; if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) { /* Need the struct to point to the realloc'd memory locations */ if( h->state == 1 ) { ParseHttpHeaders(h); ProcessHTTPPOST_upnphttp(h); } else if( h->state == 2 ) { ProcessHttpQuery_upnphttp(h); } } } break; default: DPRINTF(E_WARN, L_HTTP, "Unexpected state: %d\n", h->state); } } /* with response code and response message * also allocate enough memory */ void BuildHeader_upnphttp(struct upnphttp * h, int respcode, const char * respmsg, int bodylen) { static const char httpresphead[] = "%s %d %s\r\n" "Content-Type: %s\r\n" "Connection: close\r\n" "Content-Length: %d\r\n" "Server: " MINIDLNA_SERVER_STRING "\r\n"; time_t curtime = time(NULL); char date[30]; int templen; struct string_s res; if(!h->res_buf) { templen = sizeof(httpresphead) + 256 + bodylen; h->res_buf = (char *)malloc(templen); h->res_buf_alloclen = templen; } res.data = h->res_buf; res.size = h->res_buf_alloclen; res.off = 0; strcatf(&res, httpresphead, "HTTP/1.1", respcode, respmsg, (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"", bodylen); /* Additional headers */ if(h->respflags & FLAG_TIMEOUT) { strcatf(&res, "Timeout: Second-"); if(h->req_Timeout) { strcatf(&res, "%d\r\n", h->req_Timeout); } else { strcatf(&res, "300\r\n"); } } if(h->respflags & FLAG_SID) { strcatf(&res, "SID: %.*s\r\n", h->req_SIDLen, h->req_SID); } if(h->reqflags & FLAG_LANGUAGE) { strcatf(&res, "Content-Language: en\r\n"); } strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); strcatf(&res, "Date: %s\r\n", date); strcatf(&res, "EXT:\r\n"); strcatf(&res, "\r\n"); h->res_buflen = res.off; if(h->res_buf_alloclen < (h->res_buflen + bodylen)) { h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen)); h->res_buf_alloclen = h->res_buflen + bodylen; } } void BuildResp2_upnphttp(struct upnphttp * h, int respcode, const char * respmsg, const char * body, int bodylen) { BuildHeader_upnphttp(h, respcode, respmsg, bodylen); if( h->req_command == EHead ) return; if(body) memcpy(h->res_buf + h->res_buflen, body, bodylen); h->res_buflen += bodylen; } /* responding 200 OK ! */ void BuildResp_upnphttp(struct upnphttp *h, const char *body, int bodylen) { BuildResp2_upnphttp(h, 200, "OK", body, bodylen); } void SendResp_upnphttp(struct upnphttp * h) { int n; DPRINTF(E_DEBUG, L_HTTP, "HTTP RESPONSE: %.*s\n", h->res_buflen, h->res_buf); n = send(h->socket, h->res_buf, h->res_buflen, 0); if(n<0) { DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno)); } else if(n < h->res_buflen) { /* TODO : handle correctly this case */ DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n", n, h->res_buflen); } } static int send_data(struct upnphttp * h, char * header, size_t size, int flags) { int n; n = send(h->socket, header, size, flags); if(n<0) { DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno)); } else if(n < h->res_buflen) { /* TODO : handle correctly this case */ DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n", n, h->res_buflen); } else { return 0; } return 1; } static void send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset) { off_t send_size; off_t ret; char *buf = NULL; #if HAVE_SENDFILE int try_sendfile = 1; #endif while( offset <= end_offset ) { #if HAVE_SENDFILE if( try_sendfile ) { send_size = ( ((end_offset - offset) < MAX_BUFFER_SIZE) ? (end_offset - offset + 1) : MAX_BUFFER_SIZE); ret = sys_sendfile(h->socket, sendfd, &offset, send_size); if( ret == -1 ) { DPRINTF(E_DEBUG, L_HTTP, "sendfile error :: error no. %d [%s]\n", errno, strerror(errno)); /* If sendfile isn't supported on the filesystem, don't bother trying to use it again. */ if( errno == EOVERFLOW || errno == EINVAL ) try_sendfile = 0; else if( errno != EAGAIN ) break; } else { //DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset); continue; } } #endif /* Fall back to regular I/O */ if( !buf ) buf = malloc(MIN_BUFFER_SIZE); send_size = (((end_offset - offset) < MIN_BUFFER_SIZE) ? (end_offset - offset + 1) : MIN_BUFFER_SIZE); lseek(sendfd, offset, SEEK_SET); ret = read(sendfd, buf, send_size); if( ret == -1 ) { DPRINTF(E_DEBUG, L_HTTP, "read error :: error no. %d [%s]\n", errno, strerror(errno)); if( errno == EAGAIN ) continue; else break; } ret = write(h->socket, buf, ret); if( ret == -1 ) { DPRINTF(E_DEBUG, L_HTTP, "write error :: error no. %d [%s]\n", errno, strerror(errno)); if( errno == EAGAIN ) continue; else break; } offset += ret; } free(buf); } static void start_dlna_header(struct string_s *str, int respcode, const char *tmode, const char *mime) { char date[30]; time_t now; now = time(NULL); strftime(date, sizeof(date),"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&now)); strcatf(str, "HTTP/1.1 %d OK\r\n" "Connection: close\r\n" "Date: %s\r\n" "Server: " MINIDLNA_SERVER_STRING "\r\n" "EXT:\r\n" "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" "transferMode.dlna.org: %s\r\n" "Content-Type: %s\r\n", respcode, date, tmode, mime); } static void SendResp_icon(struct upnphttp * h, char * icon) { char header[512]; char mime[12] = "image/"; char *data; int size; struct string_s str; if( strcmp(icon, "sm.png") == 0 ) { DPRINTF(E_DEBUG, L_HTTP, "Sending small PNG icon\n"); data = (char *)png_sm; size = sizeof(png_sm)-1; strcpy(mime+6, "png"); } else if( strcmp(icon, "lrg.png") == 0 ) { DPRINTF(E_DEBUG, L_HTTP, "Sending large PNG icon\n"); data = (char *)png_lrg; size = sizeof(png_lrg)-1; strcpy(mime+6, "png"); } else if( strcmp(icon, "sm.jpg") == 0 ) { DPRINTF(E_DEBUG, L_HTTP, "Sending small JPEG icon\n"); data = (char *)jpeg_sm; size = sizeof(jpeg_sm)-1; strcpy(mime+6, "jpeg"); } else if( strcmp(icon, "lrg.jpg") == 0 ) { DPRINTF(E_DEBUG, L_HTTP, "Sending large JPEG icon\n"); data = (char *)jpeg_lrg; size = sizeof(jpeg_lrg)-1; strcpy(mime+6, "jpeg"); } else { DPRINTF(E_WARN, L_HTTP, "Invalid icon request: %s\n", icon); Send404(h); return; } INIT_STR(str, header); start_dlna_header(&str, 200, "Interactive", mime); strcatf(&str, "Content-Length: %d\r\n\r\n", size); if( send_data(h, str.data, str.off, MSG_MORE) == 0 ) { if( h->req_command != EHead ) send_data(h, data, size, 0); } CloseSocket_upnphttp(h); } static void SendResp_albumArt(struct upnphttp * h, char * object) { char header[512]; char *path; off_t size; long long id; int fd; struct string_s str; if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) ) { DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); Send406(h); return; } id = strtoll(object, NULL, 10); path = sql_get_text_field(db, "SELECT PATH from ALBUM_ART where ID = '%lld'", id); if( !path ) { DPRINTF(E_WARN, L_HTTP, "ALBUM_ART ID %s not found, responding ERROR 404\n", object); Send404(h); return; } DPRINTF(E_INFO, L_HTTP, "Serving album art ID: %lld [%s]\n", id, path); fd = open(path, O_RDONLY); if( fd < 0 ) { DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); sqlite3_free(path); Send404(h); return; } sqlite3_free(path); size = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); INIT_STR(str, header); start_dlna_header(&str, 200, "Interactive", "image/jpeg"); strcatf(&str, "Content-Length: %jd\r\n" "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n\r\n", (intmax_t)size); if( send_data(h, str.data, str.off, MSG_MORE) == 0 ) { if( h->req_command != EHead ) send_file(h, fd, 0, size-1); } close(fd); CloseSocket_upnphttp(h); } static void SendResp_caption(struct upnphttp * h, char * object) { char header[512]; char *path; off_t size; long long id; int fd; struct string_s str; id = strtoll(object, NULL, 10); path = sql_get_text_field(db, "SELECT PATH from CAPTIONS where ID = %lld", id); if( !path ) { DPRINTF(E_WARN, L_HTTP, "CAPTION ID %s not found, responding ERROR 404\n", object); Send404(h); return; } DPRINTF(E_INFO, L_HTTP, "Serving caption ID: %lld [%s]\n", id, path); fd = open(path, O_RDONLY); if( fd < 0 ) { DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); sqlite3_free(path); Send404(h); return; } sqlite3_free(path); size = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); INIT_STR(str, header); start_dlna_header(&str, 200, "Interactive", "smi/caption"); strcatf(&str, "Content-Length: %jd\r\n\r\n", (intmax_t)size); if( send_data(h, str.data, str.off, MSG_MORE) == 0 ) { if( h->req_command != EHead ) send_file(h, fd, 0, size-1); } close(fd); CloseSocket_upnphttp(h); } static void SendResp_thumbnail(struct upnphttp * h, char * object) { char header[512]; char *path; long long id; ExifData *ed; ExifLoader *l; struct string_s str; if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) ) { DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); Send406(h); return; } id = strtoll(object, NULL, 10); path = sql_get_text_field(db, "SELECT PATH from DETAILS where ID = '%lld'", id); if( !path ) { DPRINTF(E_WARN, L_HTTP, "DETAIL ID %s not found, responding ERROR 404\n", object); Send404(h); return; } DPRINTF(E_INFO, L_HTTP, "Serving thumbnail for ObjectId: %lld [%s]\n", id, path); if( access(path, F_OK) != 0 ) { DPRINTF(E_ERROR, L_HTTP, "Error accessing %s\n", path); Send404(h); sqlite3_free(path); return; } l = exif_loader_new(); exif_loader_write_file(l, path); ed = exif_loader_get_data(l); exif_loader_unref(l); sqlite3_free(path); if( !ed || !ed->size ) { Send404(h); if( ed ) exif_data_unref(ed); return; } INIT_STR(str, header); start_dlna_header(&str, 200, "Interactive", "image/jpeg"); strcatf(&str, "Content-Length: %jd\r\n" "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN;DLNA.ORG_CI=1\r\n\r\n", (intmax_t)ed->size); if( send_data(h, str.data, str.off, MSG_MORE) == 0 ) { if( h->req_command != EHead ) send_data(h, (char *)ed->data, ed->size, 0); } exif_data_unref(ed); CloseSocket_upnphttp(h); } static void SendResp_resizedimg(struct upnphttp * h, char * object) { char header[512]; char buf[128]; struct string_s str; char **result; char dlna_pn[22]; uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B|DLNA_FLAG_TM_I; int width=640, height=480, dstw, dsth, size; int srcw, srch; unsigned char * data = NULL; char *path, *file_path = NULL; char *resolution = NULL; char *key, *val; char *saveptr, *item = NULL; int rotate; int pixw = 0, pixh = 0; long long id; int rows=0, chunked, ret; image_s *imsrc = NULL, *imdst = NULL; int scale = 1; const char *tmode; id = strtoll(object, &saveptr, 10); snprintf(buf, sizeof(buf), "SELECT PATH, RESOLUTION, ROTATION from DETAILS where ID = '%lld'", (long long)id); ret = sql_get_table(db, buf, &result, &rows, NULL); if( ret != SQLITE_OK ) { Send500(h); return; } if( rows ) { file_path = result[3]; resolution = result[4]; rotate = result[5] ? atoi(result[5]) : 0; } if( !file_path || !resolution || (access(file_path, F_OK) != 0) ) { DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object); sqlite3_free_table(result); Send404(h); return; } if( saveptr ) saveptr = strchr(saveptr, '?'); path = saveptr ? saveptr + 1 : object; for( item = strtok_r(path, "&,", &saveptr); item != NULL; item = strtok_r(NULL, "&,", &saveptr) ) { decodeString(item, 1); val = item; key = strsep(&val, "="); if( !val ) continue; DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val); if( strcasecmp(key, "width") == 0 ) { width = atoi(val); } else if( strcasecmp(key, "height") == 0 ) { height = atoi(val); } else if( strcasecmp(key, "rotation") == 0 ) { rotate = (rotate + atoi(val)) % 360; sql_exec(db, "UPDATE DETAILS set ROTATION = %d where ID = %lld", rotate, id); } else if( strcasecmp(key, "pixelshape") == 0 ) { ret = sscanf(val, "%d:%d", &pixw, &pixh); if( ret != 2 ) pixw = pixh = 0; } } #if USE_FORK pid_t newpid = 0; newpid = process_fork(h->req_client); if( newpid > 0 ) { CloseSocket_upnphttp(h); goto resized_error; } #endif if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) ) { DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); Send406(h); goto resized_error; } DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %lld [%s]\n", id, file_path); if( rotate ) DPRINTF(E_DEBUG, L_HTTP, "Rotating image %d degrees\n", rotate); switch( rotate ) { case 90: ret = sscanf(resolution, "%dx%d", &srch, &srcw); rotate = ROTATE_90; break; case 270: ret = sscanf(resolution, "%dx%d", &srch, &srcw); rotate = ROTATE_270; break; case 180: ret = sscanf(resolution, "%dx%d", &srcw, &srch); rotate = ROTATE_180; break; default: ret = sscanf(resolution, "%dx%d", &srcw, &srch); rotate = ROTATE_NONE; break; } if( ret != 2 ) { Send500(h); return; } /* Figure out the best destination resolution we can use */ dstw = width; dsth = ((((width<<10)/srcw)*srch)>>10); if( dsth > height ) { dsth = height; dstw = (((height<<10)/srch) * srcw>>10); } /* Account for pixel shape */ if( pixw && pixh ) { if( pixh > pixw ) dsth = dsth * pixw / pixh; else if( pixw > pixh ) dstw = dstw * pixh / pixw; } if( dstw <= 160 && dsth <= 160 ) strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_TN;"); else if( dstw <= 640 && dsth <= 480 ) strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_SM;"); else if( dstw <= 1024 && dsth <= 768 ) strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_MED;"); else strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_LRG;"); if( srcw>>4 >= dstw && srch>>4 >= dsth) scale = 8; else if( srcw>>3 >= dstw && srch>>3 >= dsth ) scale = 4; else if( srcw>>2 >= dstw && srch>>2 >= dsth ) scale = 2; INIT_STR(str, header); #if USE_FORK if( (h->reqflags & FLAG_XFERBACKGROUND) && (setpriority(PRIO_PROCESS, 0, 19) == 0) ) tmode = "Background"; else #endif tmode = "Interactive"; start_dlna_header(&str, 200, tmode, "image/jpeg"); strcatf(&str, "contentFeatures.dlna.org: %sDLNA.ORG_CI=1;DLNA.ORG_FLAGS=%08X%024X\r\n", dlna_pn, dlna_flags, 0); if( strcmp(h->HttpVer, "HTTP/1.0") == 0 ) { chunked = 0; imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale, rotate); } else { chunked = 1; strcatf(&str, "Transfer-Encoding: chunked\r\n\r\n"); } if( !chunked ) { if( !imsrc ) { DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path); Send500(h); goto resized_error; } imdst = image_resize(imsrc, dstw, dsth); data = image_save_to_jpeg_buf(imdst, &size); strcatf(&str, "Content-Length: %d\r\n\r\n", size); } if( (send_data(h, str.data, str.off, 0) == 0) && (h->req_command != EHead) ) { if( chunked ) { imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale, rotate); if( !imsrc ) { DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path); Send500(h); goto resized_error; } imdst = image_resize(imsrc, dstw, dsth); data = image_save_to_jpeg_buf(imdst, &size); ret = sprintf(buf, "%x\r\n", size); send_data(h, buf, ret, MSG_MORE); send_data(h, (char *)data, size, MSG_MORE); send_data(h, "\r\n0\r\n\r\n", 7, 0); } else { send_data(h, (char *)data, size, 0); } } DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path); if( imsrc ) image_free(imsrc); if( imdst ) image_free(imdst); CloseSocket_upnphttp(h); resized_error: sqlite3_free_table(result); #if USE_FORK if( newpid == 0 ) _exit(0); #endif } static void SendResp_dlnafile(struct upnphttp *h, char *object) { char header[1024]; struct string_s str; char buf[128]; char **result; int rows, ret; off_t total, offset, size; int64_t id; int sendfh; uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B; uint32_t cflags = h->req_client ? h->req_client->type->flags : 0; const char *tmode; enum client_types ctype = h->req_client ? h->req_client->type->type : 0; static struct { int64_t id; enum client_types client; char path[PATH_MAX]; char mime[32]; char dlna[96]; } last_file = { 0, 0 }; #if USE_FORK pid_t newpid = 0; #endif id = strtoll(object, NULL, 10); if( cflags & FLAG_MS_PFS ) { if( strstr(object, "?albumArt=true") ) { char *art; art = sql_get_text_field(db, "SELECT ALBUM_ART from DETAILS where ID = '%lld'", id); if (art) { SendResp_albumArt(h, art); sqlite3_free(art); } else Send404(h); return; } } if( id != last_file.id || ctype != last_file.client ) { snprintf(buf, sizeof(buf), "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", (long long)id); ret = sql_get_table(db, buf, &result, &rows, NULL); if( (ret != SQLITE_OK) ) { DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", (long long)id); Send500(h); return; } if( !rows || !result[3] || !result[4] ) { DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object); sqlite3_free_table(result); Send404(h); return; } /* Cache the result */ last_file.id = id; last_file.client = ctype; strncpy(last_file.path, result[3], sizeof(last_file.path)-1); if( result[4] ) { strncpy(last_file.mime, result[4], sizeof(last_file.mime)-1); /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */ if( cflags & FLAG_SAMSUNG ) { if( strcmp(last_file.mime+6, "x-matroska") == 0 ) strcpy(last_file.mime+8, "mkv"); /* Samsung TV's such as the A750 can natively support many Xvid/DivX AVI's however, the DLNA server needs the mime type to say video/mpeg */ else if( ctype == ESamsungSeriesA && strcmp(last_file.mime+6, "x-msvideo") == 0 ) strcpy(last_file.mime+6, "mpeg"); } /* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */ else if( ctype == ESonyBDP ) { if( strcmp(last_file.mime+6, "x-matroska") == 0 || strcmp(last_file.mime+6, "mpeg") == 0 ) strcpy(last_file.mime+6, "divx"); } } if( result[5] ) snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s;", result[5]); else last_file.dlna[0] = '\0'; sqlite3_free_table(result); } #if USE_FORK newpid = process_fork(h->req_client); if( newpid > 0 ) { CloseSocket_upnphttp(h); return; } #endif DPRINTF(E_INFO, L_HTTP, "Serving DetailID: %lld [%s]\n", (long long)id, last_file.path); if( h->reqflags & FLAG_XFERSTREAMING ) { if( strncmp(last_file.mime, "image", 5) == 0 ) { DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); Send406(h); goto error; } } else if( h->reqflags & FLAG_XFERINTERACTIVE ) { if( h->reqflags & FLAG_REALTIMEINFO ) { DPRINTF(E_WARN, L_HTTP, "Bad realTimeInfo flag with Interactive request!\n"); Send400(h); goto error; } if( strncmp(last_file.mime, "image", 5) != 0 ) { DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Interactive without an image!\n"); /* Samsung TVs (well, at least the A950) do this for some reason, * and I don't see them fixing this bug any time soon. */ if( !(cflags & FLAG_SAMSUNG) || GETFLAG(DLNA_STRICT_MASK) ) { Send406(h); goto error; } } } offset = h->req_RangeStart; sendfh = open(last_file.path, O_RDONLY); if( sendfh < 0 ) { DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", last_file.path); Send404(h); goto error; } size = lseek(sendfh, 0, SEEK_END); lseek(sendfh, 0, SEEK_SET); INIT_STR(str, header); #if USE_FORK if( (h->reqflags & FLAG_XFERBACKGROUND) && (setpriority(PRIO_PROCESS, 0, 19) == 0) ) tmode = "Background"; else #endif if( strncmp(last_file.mime, "image", 5) == 0 ) tmode = "Interactive"; else tmode = "Streaming"; start_dlna_header(&str, (h->reqflags & FLAG_RANGE ? 206 : 200), tmode, last_file.mime); if( h->reqflags & FLAG_RANGE ) { if( !h->req_RangeEnd || h->req_RangeEnd == size ) { h->req_RangeEnd = size - 1; } if( (h->req_RangeStart > h->req_RangeEnd) || (h->req_RangeStart < 0) ) { DPRINTF(E_WARN, L_HTTP, "Specified range was invalid!\n"); Send400(h); close(sendfh); goto error; } if( h->req_RangeEnd >= size ) { DPRINTF(E_WARN, L_HTTP, "Specified range was outside file boundaries!\n"); Send416(h); close(sendfh); goto error; } total = h->req_RangeEnd - h->req_RangeStart + 1; strcatf(&str, "Content-Length: %jd\r\n" "Content-Range: bytes %jd-%jd/%jd\r\n", (intmax_t)total, (intmax_t)h->req_RangeStart, (intmax_t)h->req_RangeEnd, (intmax_t)size); } else { h->req_RangeEnd = size - 1; total = size; strcatf(&str, "Content-Length: %jd\r\n", (intmax_t)total); } switch( *last_file.mime ) { case 'i': dlna_flags |= DLNA_FLAG_TM_I; break; case 'a': case 'v': default: dlna_flags |= DLNA_FLAG_TM_S; break; } if( h->reqflags & FLAG_CAPTION ) { if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%lld'", (long long)id) > 0 ) strcatf(&str, "CaptionInfo.sec: http://%s:%d/Captions/%lld.srt\r\n", lan_addr[h->iface].str, runtime_vars.port, (long long)id); } strcatf(&str, "Accept-Ranges: bytes\r\n" "contentFeatures.dlna.org: %sDLNA.ORG_OP=%02X;DLNA.ORG_CI=%X;DLNA.ORG_FLAGS=%08X%024X\r\n\r\n", last_file.dlna, 1, 0, dlna_flags, 0); //DEBUG DPRINTF(E_DEBUG, L_HTTP, "RESPONSE: %s\n", str.data); if( send_data(h, str.data, str.off, MSG_MORE) == 0 ) { if( h->req_command != EHead ) send_file(h, sendfh, offset, h->req_RangeEnd); } close(sendfh); CloseSocket_upnphttp(h); error: #if USE_FORK if( newpid == 0 ) _exit(0); #endif return; } minidlna-1.1.5+dfsg/upnphttp.h000066400000000000000000000125721261774340000162760ustar00rootroot00000000000000/* MiniDLNA project * * http://sourceforge.net/projects/minidlna/ * * MiniDLNA media server * Copyright (C) 2008-2012 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . * * Portions of the code from the MiniUPnP project: * * Copyright (c) 2006-2007, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __UPNPHTTP_H__ #define __UPNPHTTP_H__ #include #include #include "minidlnatypes.h" #include "config.h" /* server: HTTP header returned in all HTTP responses : */ #define MINIDLNA_SERVER_STRING OS_VERSION " DLNADOC/1.50 UPnP/1.0 " SERVER_NAME "/" MINIDLNA_VERSION /* states : 0 - waiting for data to read 1 - waiting for HTTP Post Content. ... >= 100 - to be deleted */ enum httpCommands { EUnknown = 0, EGet, EPost, EHead, ESubscribe, EUnSubscribe }; struct upnphttp { int socket; struct in_addr clientaddr; /* client address */ int iface; int state; char HttpVer[16]; /* request */ char * req_buf; int req_buflen; int req_contentlen; int req_contentoff; /* header length */ enum httpCommands req_command; struct client_cache_s * req_client; const char * req_soapAction; int req_soapActionLen; const char * req_Callback; /* For SUBSCRIBE */ int req_CallbackLen; const char * req_NT; int req_NTLen; int req_Timeout; const char * req_SID; /* For UNSUBSCRIBE */ int req_SIDLen; off_t req_RangeStart; off_t req_RangeEnd; long int req_chunklen; uint32_t reqflags; /* response */ char * res_buf; int res_buflen; int res_buf_alloclen; uint32_t respflags; /*int res_contentlen;*/ /*int res_contentoff;*/ /* header length */ LIST_ENTRY(upnphttp) entries; }; #define FLAG_TIMEOUT 0x00000001 #define FLAG_SID 0x00000002 #define FLAG_RANGE 0x00000004 #define FLAG_HOST 0x00000008 #define FLAG_LANGUAGE 0x00000010 #define FLAG_INVALID_REQ 0x00000040 #define FLAG_HTML 0x00000080 #define FLAG_CHUNKED 0x00000100 #define FLAG_TIMESEEK 0x00000200 #define FLAG_REALTIMEINFO 0x00000400 #define FLAG_PLAYSPEED 0x00000800 #define FLAG_XFERSTREAMING 0x00001000 #define FLAG_XFERINTERACTIVE 0x00002000 #define FLAG_XFERBACKGROUND 0x00004000 #define FLAG_CAPTION 0x00008000 #ifndef MSG_MORE #define MSG_MORE 0 #endif /* New_upnphttp() */ struct upnphttp * New_upnphttp(int); /* CloseSocket_upnphttp() */ void CloseSocket_upnphttp(struct upnphttp *); /* Delete_upnphttp() */ void Delete_upnphttp(struct upnphttp *); /* Process_upnphttp() */ void Process_upnphttp(struct upnphttp *); /* BuildHeader_upnphttp() * build the header for the HTTP Response * also allocate the buffer for body data */ void BuildHeader_upnphttp(struct upnphttp * h, int respcode, const char * respmsg, int bodylen); /* BuildResp_upnphttp() * fill the res_buf buffer with the complete * HTTP 200 OK response from the body passed as argument */ void BuildResp_upnphttp(struct upnphttp *, const char *, int); /* BuildResp2_upnphttp() * same but with given response code/message */ void BuildResp2_upnphttp(struct upnphttp * h, int respcode, const char * respmsg, const char * body, int bodylen); /* Error messages */ void Send500(struct upnphttp *); void Send501(struct upnphttp *); /* SendResp_upnphttp() */ void SendResp_upnphttp(struct upnphttp *); #endif minidlna-1.1.5+dfsg/upnpreplyparse.c000066400000000000000000000102121261774340000174650ustar00rootroot00000000000000/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * * Copyright (c) 2006, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "upnpreplyparse.h" #include "minixml.h" static void NameValueParserStartElt(void * d, const char * name, int l) { struct NameValueParserData * data = (struct NameValueParserData *)d; if(l>63) l = 63; memcpy(data->curelt, name, l); data->curelt[l] = '\0'; /* store root element */ if(!data->head.lh_first) { struct NameValue * nv; nv = malloc(sizeof(struct NameValue)+l+1); strcpy(nv->name, "rootElement"); memcpy(nv->value, name, l); nv->value[l] = '\0'; LIST_INSERT_HEAD(&(data->head), nv, entries); } } static void NameValueParserGetData(void * d, const char * datas, int l) { struct NameValueParserData * data = (struct NameValueParserData *)d; struct NameValue * nv; if(l>1975) l = 1975; nv = malloc(sizeof(struct NameValue)+l+1); strncpy(nv->name, data->curelt, 64); nv->name[63] = '\0'; memcpy(nv->value, datas, l); nv->value[l] = '\0'; LIST_INSERT_HEAD(&(data->head), nv, entries); } void ParseNameValue(const char * buffer, int bufsize, struct NameValueParserData * data, uint32_t flags) { struct xmlparser parser; LIST_INIT(&(data->head)); /* init xmlparser object */ parser.xmlstart = buffer; parser.xmlsize = bufsize; parser.data = data; parser.starteltfunc = NameValueParserStartElt; parser.endeltfunc = 0; parser.datafunc = NameValueParserGetData; parser.attfunc = 0; parser.flags = flags; parsexml(&parser); } void ClearNameValueList(struct NameValueParserData * pdata) { struct NameValue * nv; while((nv = pdata->head.lh_first) != NULL) { LIST_REMOVE(nv, entries); free(nv); } } char * GetValueFromNameValueList(struct NameValueParserData * pdata, const char * Name) { struct NameValue * nv; char * p = NULL; for(nv = pdata->head.lh_first; (nv != NULL) && (p == NULL); nv = nv->entries.le_next) { if(strcmp(nv->name, Name) == 0) p = nv->value; } return p; } /* debug all-in-one function * do parsing then display to stdout */ #ifdef DEBUG void DisplayNameValueList(char * buffer, int bufsize) { struct NameValueParserData pdata; struct NameValue * nv; ParseNameValue(buffer, bufsize, &pdata); for(nv = pdata.head.lh_first; nv != NULL; nv = nv->entries.le_next) { printf("%s = %s\n", nv->name, nv->value); } ClearNameValueList(&pdata); } #endif minidlna-1.1.5+dfsg/upnpreplyparse.h000066400000000000000000000052041261774340000174770ustar00rootroot00000000000000/* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * * Copyright (c) 2006, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __UPNPREPLYPARSE_H__ #define __UPNPREPLYPARSE_H__ #include #include #ifdef __cplusplus extern "C" { #endif struct NameValue { LIST_ENTRY(NameValue) entries; char name[64]; char value[]; }; struct NameValueParserData { LIST_HEAD(listhead, NameValue) head; char curelt[64]; }; #define XML_STORE_EMPTY_FL 0x01 /* ParseNameValue() */ void ParseNameValue(const char * buffer, int bufsize, struct NameValueParserData * data, uint32_t flags); /* ClearNameValueList() */ void ClearNameValueList(struct NameValueParserData * pdata); /* GetValueFromNameValueList() */ char * GetValueFromNameValueList(struct NameValueParserData * pdata, const char * Name); /* GetValueFromNameValueListIgnoreNS() */ char * GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, const char * Name); /* DisplayNameValueList() */ #ifdef DEBUG void DisplayNameValueList(char * buffer, int bufsize); #endif #ifdef __cplusplus } #endif #endif minidlna-1.1.5+dfsg/upnpsoap.c000066400000000000000000001675751261774340000162720ustar00rootroot00000000000000/* MiniDLNA project * * http://sourceforge.net/projects/minidlna/ * * MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . * * Portions of the code from the MiniUPnP project: * * Copyright (c) 2006-2007, Thomas Bernard * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "upnpglobalvars.h" #include "utils.h" #include "upnphttp.h" #include "upnpsoap.h" #include "containers.h" #include "upnpreplyparse.h" #include "getifaddr.h" #include "scanner.h" #include "sql.h" #include "log.h" #ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */ # define __SORT_LIMIT if( totalMatches < 10000 ) #else # define __SORT_LIMIT #endif /* Standard Errors: * * errorCode errorDescription Description * -------- ---------------- ----------- * 401 Invalid Action No action by that name at this service. * 402 Invalid Args Could be any of the following: not enough in args, * too many in args, no in arg by that name, * one or more in args are of the wrong data type. * 403 Out of Sync Out of synchronization. * 501 Action Failed May be returned in current state of service * prevents invoking that action. * 600-699 TBD Common action errors. Defined by UPnP Forum * Technical Committee. * 700-799 TBD Action-specific errors for standard actions. * Defined by UPnP Forum working committee. * 800-899 TBD Action-specific errors for non-standard actions. * Defined by UPnP vendor. */ static void SoapError(struct upnphttp * h, int errCode, const char * errDesc) { static const char resp[] = "" "" "" "s:Client" "UPnPError" "" "" "%d" "%s" "" "" "" "" ""; char body[2048]; int bodylen; DPRINTF(E_WARN, L_HTTP, "Returning UPnPError %d: %s\n", errCode, errDesc); bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc); BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen); SendResp_upnphttp(h); CloseSocket_upnphttp(h); } static void BuildSendAndCloseSoapResp(struct upnphttp * h, const char * body, int bodylen) { static const char beforebody[] = "\r\n" "" ""; static const char afterbody[] = "" "\r\n"; if (!body || bodylen < 0) { Send500(h); return; } BuildHeader_upnphttp(h, 200, "OK", sizeof(beforebody) - 1 + sizeof(afterbody) - 1 + bodylen ); memcpy(h->res_buf + h->res_buflen, beforebody, sizeof(beforebody) - 1); h->res_buflen += sizeof(beforebody) - 1; memcpy(h->res_buf + h->res_buflen, body, bodylen); h->res_buflen += bodylen; memcpy(h->res_buf + h->res_buflen, afterbody, sizeof(afterbody) - 1); h->res_buflen += sizeof(afterbody) - 1; SendResp_upnphttp(h); CloseSocket_upnphttp(h); } static void GetSystemUpdateID(struct upnphttp * h, const char * action) { static const char resp[] = "" "%d" ""; char body[512]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:ContentDirectory:1", updateID, action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void IsAuthorizedValidated(struct upnphttp * h, const char * action) { static const char resp[] = "" "%d" ""; char body[512]; struct NameValueParserData data; const char * id; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, XML_STORE_EMPTY_FL); id = GetValueFromNameValueList(&data, "DeviceID"); if(id) { int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1", 1, action); BuildSendAndCloseSoapResp(h, body, bodylen); } else SoapError(h, 402, "Invalid Args"); ClearNameValueList(&data); } static void RegisterDevice(struct upnphttp * h, const char * action) { static const char resp[] = "" "%s" ""; char body[512]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1", uuidvalue, action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetProtocolInfo(struct upnphttp * h, const char * action) { static const char resp[] = "" "" RESOURCE_PROTOCOL_INFO_VALUES "" "" ""; char * body; int bodylen; bodylen = asprintf(&body, resp, action, "urn:schemas-upnp-org:service:ConnectionManager:1", action); BuildSendAndCloseSoapResp(h, body, bodylen); free(body); } static void GetSortCapabilities(struct upnphttp * h, const char * action) { static const char resp[] = "" "" "dc:title," "dc:date," "upnp:class," "upnp:album," "upnp:originalTrackNumber" "" ""; char body[512]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:ContentDirectory:1", action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetSearchCapabilities(struct upnphttp * h, const char * action) { static const char resp[] = "" "" "dc:creator," "dc:date," "dc:title," "upnp:album," "upnp:actor," "upnp:artist," "upnp:class," "upnp:genre," "@id," "@parentID," "@refID" "" ""; char body[512]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:ContentDirectory:1", action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetCurrentConnectionIDs(struct upnphttp * h, const char * action) { /* TODO: Use real data. - JM */ static const char resp[] = "" "0" ""; char body[512]; int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:ConnectionManager:1", action); BuildSendAndCloseSoapResp(h, body, bodylen); } static void GetCurrentConnectionInfo(struct upnphttp * h, const char * action) { /* TODO: Use real data. - JM */ static const char resp[] = "" "-1" "-1" "" "" "-1" "Output" "Unknown" ""; char body[sizeof(resp)+128]; struct NameValueParserData data; const char *id_str; int id; char *endptr = NULL; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, XML_STORE_EMPTY_FL); id_str = GetValueFromNameValueList(&data, "ConnectionID"); DPRINTF(E_INFO, L_HTTP, "GetCurrentConnectionInfo(%s)\n", id_str); if(id_str) id = strtol(id_str, &endptr, 10); if (!id_str || endptr == id_str) { SoapError(h, 402, "Invalid Args"); } else if(id != 0) { SoapError(h, 701, "No such object error"); } else { int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:service:ConnectionManager:1", action); BuildSendAndCloseSoapResp(h, body, bodylen); } ClearNameValueList(&data); } /* Standard DLNA/UPnP filter flags */ #define FILTER_CHILDCOUNT 0x00000001 #define FILTER_DC_CREATOR 0x00000002 #define FILTER_DC_DATE 0x00000004 #define FILTER_DC_DESCRIPTION 0x00000008 #define FILTER_DLNA_NAMESPACE 0x00000010 #define FILTER_REFID 0x00000020 #define FILTER_RES 0x00000040 #define FILTER_RES_BITRATE 0x00000080 #define FILTER_RES_DURATION 0x00000100 #define FILTER_RES_NRAUDIOCHANNELS 0x00000200 #define FILTER_RES_RESOLUTION 0x00000400 #define FILTER_RES_SAMPLEFREQUENCY 0x00000800 #define FILTER_RES_SIZE 0x00001000 #define FILTER_SEARCHABLE 0x00002000 #define FILTER_UPNP_ACTOR 0x00004000 #define FILTER_UPNP_ALBUM 0x00008000 #define FILTER_UPNP_ALBUMARTURI 0x00010000 #define FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID 0x00020000 #define FILTER_UPNP_ARTIST 0x00040000 #define FILTER_UPNP_GENRE 0x00080000 #define FILTER_UPNP_ORIGINALTRACKNUMBER 0x00100000 #define FILTER_UPNP_SEARCHCLASS 0x00200000 #define FILTER_UPNP_STORAGEUSED 0x00400000 /* Vendor-specific filter flags */ #define FILTER_SEC_CAPTION_INFO_EX 0x01000000 #define FILTER_SEC_DCM_INFO 0x02000000 #define FILTER_PV_SUBTITLE_FILE_TYPE 0x04000000 #define FILTER_PV_SUBTITLE_FILE_URI 0x08000000 #define FILTER_PV_SUBTITLE 0x0C000000 #define FILTER_AV_MEDIA_CLASS 0x10000000 static uint32_t set_filter_flags(char *filter, struct upnphttp *h) { char *item, *saveptr = NULL; uint32_t flags = 0; int samsung = h->req_client && (h->req_client->type->flags & FLAG_SAMSUNG); if( !filter || (strlen(filter) <= 1) ) { /* Not the full 32 bits. Skip vendor-specific stuff by default. */ flags = 0xFFFFFF; if (samsung) flags |= FILTER_SEC_CAPTION_INFO_EX | FILTER_SEC_DCM_INFO; } if (flags) return flags; if( samsung ) flags |= FILTER_DLNA_NAMESPACE; item = strtok_r(filter, ",", &saveptr); while( item != NULL ) { if( saveptr ) *(item-1) = ','; while( isspace(*item) ) item++; if( strcmp(item, "@childCount") == 0 ) { flags |= FILTER_CHILDCOUNT; } else if( strcmp(item, "@searchable") == 0 ) { flags |= FILTER_SEARCHABLE; } else if( strcmp(item, "dc:creator") == 0 ) { flags |= FILTER_DC_CREATOR; } else if( strcmp(item, "dc:date") == 0 ) { flags |= FILTER_DC_DATE; } else if( strcmp(item, "dc:description") == 0 ) { flags |= FILTER_DC_DESCRIPTION; } else if( strcmp(item, "dlna") == 0 ) { flags |= FILTER_DLNA_NAMESPACE; } else if( strcmp(item, "@refID") == 0 ) { flags |= FILTER_REFID; } else if( strcmp(item, "upnp:album") == 0 ) { flags |= FILTER_UPNP_ALBUM; } else if( strcmp(item, "upnp:albumArtURI") == 0 ) { flags |= FILTER_UPNP_ALBUMARTURI; if( samsung ) flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID; } else if( strcmp(item, "upnp:albumArtURI@dlna:profileID") == 0 ) { flags |= FILTER_UPNP_ALBUMARTURI; flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID; } else if( strcmp(item, "upnp:artist") == 0 ) { flags |= FILTER_UPNP_ARTIST; } else if( strcmp(item, "upnp:actor") == 0 ) { flags |= FILTER_UPNP_ACTOR; } else if( strcmp(item, "upnp:genre") == 0 ) { flags |= FILTER_UPNP_GENRE; } else if( strcmp(item, "upnp:originalTrackNumber") == 0 ) { flags |= FILTER_UPNP_ORIGINALTRACKNUMBER; } else if( strcmp(item, "upnp:searchClass") == 0 ) { flags |= FILTER_UPNP_SEARCHCLASS; } else if( strcmp(item, "upnp:storageUsed") == 0 ) { flags |= FILTER_UPNP_STORAGEUSED; } else if( strcmp(item, "res") == 0 ) { flags |= FILTER_RES; } else if( (strcmp(item, "res@bitrate") == 0) || (strcmp(item, "@bitrate") == 0) || ((strcmp(item, "bitrate") == 0) && (flags & FILTER_RES)) ) { flags |= FILTER_RES; flags |= FILTER_RES_BITRATE; } else if( (strcmp(item, "res@duration") == 0) || (strcmp(item, "@duration") == 0) || ((strcmp(item, "duration") == 0) && (flags & FILTER_RES)) ) { flags |= FILTER_RES; flags |= FILTER_RES_DURATION; } else if( (strcmp(item, "res@nrAudioChannels") == 0) || (strcmp(item, "@nrAudioChannels") == 0) || ((strcmp(item, "nrAudioChannels") == 0) && (flags & FILTER_RES)) ) { flags |= FILTER_RES; flags |= FILTER_RES_NRAUDIOCHANNELS; } else if( (strcmp(item, "res@resolution") == 0) || (strcmp(item, "@resolution") == 0) || ((strcmp(item, "resolution") == 0) && (flags & FILTER_RES)) ) { flags |= FILTER_RES; flags |= FILTER_RES_RESOLUTION; } else if( (strcmp(item, "res@sampleFrequency") == 0) || (strcmp(item, "@sampleFrequency") == 0) || ((strcmp(item, "sampleFrequency") == 0) && (flags & FILTER_RES)) ) { flags |= FILTER_RES; flags |= FILTER_RES_SAMPLEFREQUENCY; } else if( (strcmp(item, "res@size") == 0) || (strcmp(item, "@size") == 0) || (strcmp(item, "size") == 0) ) { flags |= FILTER_RES; flags |= FILTER_RES_SIZE; } else if( strcmp(item, "sec:CaptionInfoEx") == 0 ) { flags |= FILTER_SEC_CAPTION_INFO_EX; } else if( strcmp(item, "sec:dcmInfo") == 0 ) { flags |= FILTER_SEC_DCM_INFO; } else if( strcmp(item, "res@pv:subtitleFileType") == 0 ) { flags |= FILTER_PV_SUBTITLE_FILE_TYPE; } else if( strcmp(item, "res@pv:subtitleFileUri") == 0 ) { flags |= FILTER_PV_SUBTITLE_FILE_URI; } else if( strcmp(item, "av:mediaClass") == 0 ) { flags |= FILTER_AV_MEDIA_CLASS; } item = strtok_r(NULL, ",", &saveptr); } return flags; } static char * parse_sort_criteria(char *sortCriteria, int *error) { char *order = NULL; char *item, *saveptr; int i, ret, reverse, title_sorted = 0; struct string_s str; *error = 0; if( force_sort_criteria ) sortCriteria = strdup(force_sort_criteria); if( !sortCriteria ) return NULL; if( (item = strtok_r(sortCriteria, ",", &saveptr)) ) { order = malloc(4096); str.data = order; str.size = 4096; str.off = 0; strcatf(&str, "order by "); } for( i = 0; item != NULL; i++ ) { reverse=0; if( i ) strcatf(&str, ", "); if( *item == '+' ) { item++; } else if( *item == '-' ) { reverse = 1; item++; } else { DPRINTF(E_ERROR, L_HTTP, "No order specified [%s]\n", item); goto bad_direction; } if( strcasecmp(item, "upnp:class") == 0 ) { strcatf(&str, "o.CLASS"); } else if( strcasecmp(item, "dc:title") == 0 ) { strcatf(&str, "d.TITLE"); title_sorted = 1; } else if( strcasecmp(item, "dc:date") == 0 ) { strcatf(&str, "d.DATE"); } else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 ) { strcatf(&str, "d.DISC, d.TRACK"); } else if( strcasecmp(item, "upnp:album") == 0 ) { strcatf(&str, "d.ALBUM"); } else { DPRINTF(E_ERROR, L_HTTP, "Unhandled SortCriteria [%s]\n", item); bad_direction: *error = -1; if( i ) { ret = strlen(order); order[ret-2] = '\0'; } i--; goto unhandled_order; } if( reverse ) strcatf(&str, " DESC"); unhandled_order: item = strtok_r(NULL, ",", &saveptr); } if( i <= 0 ) { free(order); if( force_sort_criteria ) free(sortCriteria); return NULL; } /* Add a "tiebreaker" sort order */ if( !title_sorted ) strcatf(&str, ", TITLE ASC"); if( force_sort_criteria ) free(sortCriteria); return order; } inline static void add_resized_res(int srcw, int srch, int reqw, int reqh, char *dlna_pn, char *detailID, struct Response *args) { int dstw = reqw; int dsth = reqh; if( (args->flags & FLAG_NO_RESIZE) && reqw > 160 && reqh > 160 ) return; strcatf(args->str, "<res "); if( args->filter & FILTER_RES_RESOLUTION ) { dstw = reqw; dsth = ((((reqw<<10)/srcw)*srch)>>10); if( dsth > reqh ) { dsth = reqh; dstw = (((reqh<<10)/srch) * srcw>>10); } strcatf(args->str, "resolution=\"%dx%d\" ", dstw, dsth); } strcatf(args->str, "protocolInfo=\"http-get:*:image/jpeg:" "DLNA.ORG_PN=%s;DLNA.ORG_CI=1;DLNA.ORG_FLAGS=%08X%024X\">" "http://%s:%d/Resized/%s.jpg?width=%d,height=%d" "</res>", dlna_pn, DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B|DLNA_FLAG_TM_I, 0, lan_addr[args->iface].str, runtime_vars.port, detailID, dstw, dsth); } inline static void add_res(char *size, char *duration, char *bitrate, char *sampleFrequency, char *nrAudioChannels, char *resolution, char *dlna_pn, char *mime, char *detailID, const char *ext, struct Response *args) { strcatf(args->str, "<res "); if( size && (args->filter & FILTER_RES_SIZE) ) { strcatf(args->str, "size=\"%s\" ", size); } if( duration && (args->filter & FILTER_RES_DURATION) ) { strcatf(args->str, "duration=\"%s\" ", duration); } if( bitrate && (args->filter & FILTER_RES_BITRATE) ) { int br = atoi(bitrate); if(args->flags & FLAG_MS_PFS) br /= 8; strcatf(args->str, "bitrate=\"%d\" ", br); } if( sampleFrequency && (args->filter & FILTER_RES_SAMPLEFREQUENCY) ) { strcatf(args->str, "sampleFrequency=\"%s\" ", sampleFrequency); } if( nrAudioChannels && (args->filter & FILTER_RES_NRAUDIOCHANNELS) ) { strcatf(args->str, "nrAudioChannels=\"%s\" ", nrAudioChannels); } if( resolution && (args->filter & FILTER_RES_RESOLUTION) ) { strcatf(args->str, "resolution=\"%s\" ", resolution); } if( args->filter & FILTER_PV_SUBTITLE ) { if( args->flags & FLAG_HAS_CAPTIONS ) { if( args->filter & FILTER_PV_SUBTITLE_FILE_TYPE ) strcatf(args->str, "pv:subtitleFileType=\"SRT\" "); if( args->filter & FILTER_PV_SUBTITLE_FILE_URI ) strcatf(args->str, "pv:subtitleFileUri=\"http://%s:%d/Captions/%s.srt\" ", lan_addr[args->iface].str, runtime_vars.port, detailID); } } strcatf(args->str, "protocolInfo=\"http-get:*:%s:%s\">" "http://%s:%d/MediaItems/%s.%s" "</res>", mime, dlna_pn, lan_addr[args->iface].str, runtime_vars.port, detailID, ext); } static int get_child_count(const char *object, struct magic_container_s *magic) { int ret; if (magic && magic->child_count) ret = sql_get_int_field(db, "SELECT count(*) from %s", magic->child_count); else if (magic && magic->objectid && *(magic->objectid)) ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", *(magic->objectid)); else ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", object); return (ret > 0) ? ret : 0; } static int object_exists(const char *object) { int ret; ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'", strcmp(object, "*") == 0 ? "0" : object); return (ret > 0); } #define COLUMNS "o.DETAIL_ID, o.CLASS," \ " d.SIZE, d.TITLE, d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST," \ " d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RESOLUTION," \ " d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.ROTATION, d.DISC " #define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, o.REF_ID, " COLUMNS #define NON_ZERO(x) (x && atoi(x)) #define IS_ZERO(x) (!x || !atoi(x)) static int callback(void *args, int argc, char **argv, char **azColName) { struct Response *passed_args = (struct Response *)args; char *id = argv[0], *parent = argv[1], *refID = argv[2], *detailID = argv[3], *class = argv[4], *size = argv[5], *title = argv[6], *duration = argv[7], *bitrate = argv[8], *sampleFrequency = argv[9], *artist = argv[10], *album = argv[11], *genre = argv[12], *comment = argv[13], *nrAudioChannels = argv[14], *track = argv[15], *date = argv[16], *resolution = argv[17], *tn = argv[18], *creator = argv[19], *dlna_pn = argv[20], *mime = argv[21], *album_art = argv[22], *rotate = argv[23]; char dlna_buf[128]; const char *ext; struct string_s *str = passed_args->str; int ret = 0; /* Make sure we have at least 8KB left of allocated memory to finish the response. */ if( str->off > (str->size - 8192) ) { #if MAX_RESPONSE_SIZE > 0 if( (str->size+DEFAULT_RESP_SIZE) <= MAX_RESPONSE_SIZE ) { #endif str->data = realloc(str->data, (str->size+DEFAULT_RESP_SIZE)); if( str->data ) { str->size += DEFAULT_RESP_SIZE; DPRINTF(E_DEBUG, L_HTTP, "UPnP SOAP response enlarged to %lu. [%d results so far]\n", (unsigned long)str->size, passed_args->returned); } else { DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response was too big, and realloc failed!\n"); return -1; } #if MAX_RESPONSE_SIZE > 0 } else { DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response cut short, to not exceed the max response size [%lld]!\n", (long long int)MAX_RESPONSE_SIZE); return -1; } #endif } passed_args->returned++; passed_args->flags &= ~RESPONSE_FLAGS; if( strncmp(class, "item", 4) == 0 ) { uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B; char *alt_title = NULL; /* We may need special handling for certain MIME types */ if( *mime == 'v' ) { dlna_flags |= DLNA_FLAG_TM_S; if( passed_args->flags & FLAG_MIME_AVI_DIVX ) { if( strcmp(mime, "video/x-msvideo") == 0 ) { if( creator ) strcpy(mime+6, "divx"); else strcpy(mime+6, "avi"); } } else if( passed_args->flags & FLAG_MIME_AVI_AVI ) { if( strcmp(mime, "video/x-msvideo") == 0 ) { strcpy(mime+6, "avi"); } } else if( passed_args->client == EFreeBox && dlna_pn ) { if( strncmp(dlna_pn, "AVC_TS", 6) == 0 || strncmp(dlna_pn, "MPEG_TS", 7) == 0 ) { strcpy(mime+6, "mp2t"); } } if( !(passed_args->flags & FLAG_DLNA) ) { if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 ) { strcpy(mime+6, "mpeg"); } } if( (passed_args->flags & FLAG_CAPTION_RES) || (passed_args->filter & (FILTER_SEC_CAPTION_INFO_EX|FILTER_PV_SUBTITLE)) ) { if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%s'", detailID) > 0 ) passed_args->flags |= FLAG_HAS_CAPTIONS; } /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */ if( passed_args->flags & FLAG_SAMSUNG ) { if( strcmp(mime+6, "x-matroska") == 0 ) { strcpy(mime+8, "mkv"); } } /* LG hack: subtitles won't get used unless dc:title contains a dot. */ else if( passed_args->client == ELGDevice && (passed_args->flags & FLAG_HAS_CAPTIONS) ) { ret = asprintf(&alt_title, "%s.", title); if( ret > 0 ) title = alt_title; else alt_title = NULL; } /* Asus OPlay reboots with titles longer than 23 characters with some file types. */ else if( passed_args->client == EAsusOPlay && (passed_args->flags & FLAG_HAS_CAPTIONS) ) { if( strlen(title) > 23 ) title[23] = '\0'; } } else if( *mime == 'a' ) { dlna_flags |= DLNA_FLAG_TM_S; if( strcmp(mime+6, "x-flac") == 0 ) { if( passed_args->flags & FLAG_MIME_FLAC_FLAC ) { strcpy(mime+6, "flac"); } } else if( strcmp(mime+6, "x-wav") == 0 ) { if( passed_args->flags & FLAG_MIME_WAV_WAV ) { strcpy(mime+6, "wav"); } } } else dlna_flags |= DLNA_FLAG_TM_I; if( dlna_pn ) snprintf(dlna_buf, sizeof(dlna_buf), "DLNA.ORG_PN=%s;" "DLNA.ORG_OP=01;" "DLNA.ORG_CI=0;" "DLNA.ORG_FLAGS=%08X%024X", dlna_pn, dlna_flags, 0); else if( passed_args->flags & FLAG_DLNA ) snprintf(dlna_buf, sizeof(dlna_buf), "DLNA.ORG_OP=01;" "DLNA.ORG_CI=0;" "DLNA.ORG_FLAGS=%08X%024X", dlna_flags, 0); else strcpy(dlna_buf, "*"); ret = strcatf(str, "<item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent); if( refID && (passed_args->filter & FILTER_REFID) ) { ret = strcatf(str, " refID=\"%s\"", refID); } ret = strcatf(str, ">" "<dc:title>%s</dc:title>" "<upnp:class>object.%s</upnp:class>", title, class); if( comment && (passed_args->filter & FILTER_DC_DESCRIPTION) ) { ret = strcatf(str, "<dc:description>%.384s</dc:description>", comment); } if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) { ret = strcatf(str, "<dc:creator>%s</dc:creator>", creator); } if( date && (passed_args->filter & FILTER_DC_DATE) ) { ret = strcatf(str, "<dc:date>%s</dc:date>", date); } if( passed_args->filter & FILTER_SEC_DCM_INFO ) { /* Get bookmark */ ret = strcatf(str, "<sec:dcmInfo>CREATIONDATE=0,FOLDER=%s,BM=%d</sec:dcmInfo>", title, sql_get_int_field(db, "SELECT SEC from BOOKMARKS where ID = '%s'", detailID)); } if( artist ) { if( (*mime == 'v') && (passed_args->filter & FILTER_UPNP_ACTOR) ) { ret = strcatf(str, "<upnp:actor>%s</upnp:actor>", artist); } if( passed_args->filter & FILTER_UPNP_ARTIST ) { ret = strcatf(str, "<upnp:artist>%s</upnp:artist>", artist); } } if( album && (passed_args->filter & FILTER_UPNP_ALBUM) ) { ret = strcatf(str, "<upnp:album>%s</upnp:album>", album); } if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) { ret = strcatf(str, "<upnp:genre>%s</upnp:genre>", genre); } if( strncmp(id, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) { track = strrchr(id, '$')+1; } if( NON_ZERO(track) && (passed_args->filter & FILTER_UPNP_ORIGINALTRACKNUMBER) ) { ret = strcatf(str, "<upnp:originalTrackNumber>%s</upnp:originalTrackNumber>", track); } if( passed_args->filter & FILTER_RES ) { ext = mime_to_ext(mime); add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, resolution, dlna_buf, mime, detailID, ext, passed_args); if( *mime == 'i' ) { int srcw, srch; if( resolution && (sscanf(resolution, "%6dx%6d", &srcw, &srch) == 2) ) { if( srcw > 4096 || srch > 4096 ) add_resized_res(srcw, srch, 4096, 4096, "JPEG_LRG", detailID, passed_args); if( srcw > 1024 || srch > 768 ) add_resized_res(srcw, srch, 1024, 768, "JPEG_MED", detailID, passed_args); if( srcw > 640 || srch > 480 ) add_resized_res(srcw, srch, 640, 480, "JPEG_SM", detailID, passed_args); } if( !(passed_args->flags & FLAG_RESIZE_THUMBS) && NON_ZERO(tn) && IS_ZERO(rotate) ) { ret = strcatf(str, "<res protocolInfo=\"http-get:*:%s:%s\">" "http://%s:%d/Thumbnails/%s.jpg" "</res>", mime, "DLNA.ORG_PN=JPEG_TN;DLNA.ORG_CI=1", lan_addr[passed_args->iface].str, runtime_vars.port, detailID); } else add_resized_res(srcw, srch, 160, 160, "JPEG_TN", detailID, passed_args); } else if( *mime == 'v' ) { switch( passed_args->client ) { case EToshibaTV: if( dlna_pn && (strncmp(dlna_pn, "MPEG_TS_HD_NA", 13) == 0 || strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) == 0 || strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 || strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0)) { sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_PS_NTSC"); add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, resolution, dlna_buf, mime, detailID, ext, passed_args); } break; case ESonyBDP: if( dlna_pn && (strncmp(dlna_pn, "AVC_TS", 6) == 0 || strncmp(dlna_pn, "MPEG_TS", 7) == 0) ) { if( strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) != 0 ) { sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_TS_SD_NA"); add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, resolution, dlna_buf, mime, detailID, ext, passed_args); } if( strncmp(dlna_pn, "MPEG_TS_SD_EU", 13) != 0 ) { sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_TS_SD_EU"); add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, resolution, dlna_buf, mime, detailID, ext, passed_args); } } else if( (dlna_pn && (strncmp(dlna_pn, "AVC_MP4", 7) == 0 || strncmp(dlna_pn, "MPEG4_P2_MP4", 12) == 0)) || strcmp(mime+6, "x-matroska") == 0 || strcmp(mime+6, "x-msvideo") == 0 || strcmp(mime+6, "mpeg") == 0 ) { strcpy(mime+6, "avi"); if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_NTSC", 12) != 0 ) { sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_PS_NTSC"); add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, resolution, dlna_buf, mime, detailID, ext, passed_args); } if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_PAL", 11) != 0 ) { sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_PS_PAL"); add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, resolution, dlna_buf, mime, detailID, ext, passed_args); } } break; case ESonyBravia: /* BRAVIA KDL-##*X### series TVs do natively support AVC/AC3 in TS, but require profile to be renamed (applies to _T and _ISO variants also) */ if( dlna_pn && (strncmp(dlna_pn, "AVC_TS_MP_SD_AC3", 16) == 0 || strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 || strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0)) { sprintf(dlna_buf, "DLNA.ORG_PN=AVC_TS_HD_50_AC3%s", dlna_pn + 16); add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels, resolution, dlna_buf, mime, detailID, ext, passed_args); } break; case ESamsungSeriesCDE: case ELGDevice: case EAsusOPlay: default: if( passed_args->flags & FLAG_HAS_CAPTIONS ) { if( passed_args->flags & FLAG_CAPTION_RES ) ret = strcatf(str, "<res protocolInfo=\"http-get:*:text/srt:*\">" "http://%s:%d/Captions/%s.srt" "</res>", lan_addr[passed_args->iface].str, runtime_vars.port, detailID); else if( passed_args->filter & FILTER_SEC_CAPTION_INFO_EX ) ret = strcatf(str, "<sec:CaptionInfoEx sec:type=\"srt\">" "http://%s:%d/Captions/%s.srt" "</sec:CaptionInfoEx>", lan_addr[passed_args->iface].str, runtime_vars.port, detailID); } free(alt_title); break; } } } if( NON_ZERO(album_art) ) { /* Video and audio album art is handled differently */ if( *mime == 'v' && (passed_args->filter & FILTER_RES) && !(passed_args->flags & FLAG_MS_PFS) ) { ret = strcatf(str, "<res protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\">" "http://%s:%d/AlbumArt/%s-%s.jpg" "</res>", lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID); } else if( passed_args->filter & FILTER_UPNP_ALBUMARTURI ) { ret = strcatf(str, "<upnp:albumArtURI"); if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) { ret = strcatf(str, " dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\""); } ret = strcatf(str, ">http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>", lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID); } } if( (passed_args->flags & FLAG_MS_PFS) && *mime == 'i' ) { if( passed_args->client == EMediaRoom && !album ) ret = strcatf(str, "<upnp:album>%s</upnp:album>", "[No Keywords]"); /* EVA2000 doesn't seem to handle embedded thumbnails */ if( !(passed_args->flags & FLAG_RESIZE_THUMBS) && NON_ZERO(tn) && IS_ZERO(rotate) ) { ret = strcatf(str, "<upnp:albumArtURI>" "http://%s:%d/Thumbnails/%s.jpg" "</upnp:albumArtURI>", lan_addr[passed_args->iface].str, runtime_vars.port, detailID); } else { ret = strcatf(str, "<upnp:albumArtURI>" "http://%s:%d/Resized/%s.jpg?width=160,height=160" "</upnp:albumArtURI>", lan_addr[passed_args->iface].str, runtime_vars.port, detailID); } } ret = strcatf(str, "</item>"); } else if( strncmp(class, "container", 9) == 0 ) { ret = strcatf(str, "<container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent); if( passed_args->filter & FILTER_SEARCHABLE ) { ret = strcatf(str, "searchable=\"%d\" ", check_magic_container(id, passed_args->flags) ? 0 : 1); } if( passed_args->filter & FILTER_CHILDCOUNT ) { ret = strcatf(str, "childCount=\"%d\"", get_child_count(id, check_magic_container(id, passed_args->flags))); } /* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */ if( passed_args->requested == 1 && strcmp(id, "0") == 0 && (passed_args->filter & FILTER_UPNP_SEARCHCLASS) ) { ret = strcatf(str, ">" "<upnp:searchClass includeDerived=\"1\">object.item.audioItem</upnp:searchClass>" "<upnp:searchClass includeDerived=\"1\">object.item.imageItem</upnp:searchClass>" "<upnp:searchClass includeDerived=\"1\">object.item.videoItem</upnp:searchClass"); } ret = strcatf(str, ">" "<dc:title>%s</dc:title>" "<upnp:class>object.%s</upnp:class>", title, class); if( (passed_args->filter & FILTER_UPNP_STORAGEUSED) || strcmp(class+10, "storageFolder") == 0 ) { /* TODO: Implement real folder size tracking */ ret = strcatf(str, "<upnp:storageUsed>%s</upnp:storageUsed>", (size ? size : "-1")); } if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) { ret = strcatf(str, "<dc:creator>%s</dc:creator>", creator); } if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) { ret = strcatf(str, "<upnp:genre>%s</upnp:genre>", genre); } if( artist && (passed_args->filter & FILTER_UPNP_ARTIST) ) { ret = strcatf(str, "<upnp:artist>%s</upnp:artist>", artist); } if( NON_ZERO(album_art) && (passed_args->filter & FILTER_UPNP_ALBUMARTURI) ) { ret = strcatf(str, "<upnp:albumArtURI "); if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) { ret = strcatf(str, "dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\""); } ret = strcatf(str, ">http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>", lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID); } if( passed_args->filter & FILTER_AV_MEDIA_CLASS ) { char class; if( strncmp(id, MUSIC_ID, sizeof(MUSIC_ID)) == 0 ) class = 'M'; else if( strncmp(id, VIDEO_ID, sizeof(VIDEO_ID)) == 0 ) class = 'V'; else if( strncmp(id, IMAGE_ID, sizeof(IMAGE_ID)) == 0 ) class = 'P'; else class = 0; if( class ) ret = strcatf(str, "<av:mediaClass xmlns:av=\"urn:schemas-sony-com:av\">" "%c</av:mediaClass>", class); } ret = strcatf(str, "</container>"); } return 0; } static void BrowseContentDirectory(struct upnphttp * h, const char * action) { static const char resp0[] = "" "" "<DIDL-Lite" CONTENT_DIRECTORY_SCHEMAS; struct magic_container_s *magic; char *zErrMsg = NULL; char *sql, *ptr; struct Response args; struct string_s str; int totalMatches = 0; int ret; const char *ObjectID, *BrowseFlag; char *Filter, *SortCriteria; const char *objectid_sql = "o.OBJECT_ID"; const char *parentid_sql = "o.PARENT_ID"; const char *refid_sql = "o.REF_ID"; char where[256] = ""; char *orderBy = NULL; struct NameValueParserData data; int RequestedCount = 0; int StartingIndex = 0; memset(&args, 0, sizeof(args)); memset(&str, 0, sizeof(str)); ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, 0); ObjectID = GetValueFromNameValueList(&data, "ObjectID"); Filter = GetValueFromNameValueList(&data, "Filter"); BrowseFlag = GetValueFromNameValueList(&data, "BrowseFlag"); SortCriteria = GetValueFromNameValueList(&data, "SortCriteria"); if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) ) RequestedCount = atoi(ptr); if( RequestedCount < 0 ) { SoapError(h, 402, "Invalid Args"); goto browse_error; } if( !RequestedCount ) RequestedCount = -1; if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) ) StartingIndex = atoi(ptr); if( StartingIndex < 0 ) { SoapError(h, 402, "Invalid Args"); goto browse_error; } if( !BrowseFlag || (strcmp(BrowseFlag, "BrowseDirectChildren") && strcmp(BrowseFlag, "BrowseMetadata")) ) { SoapError(h, 402, "Invalid Args"); goto browse_error; } if( !ObjectID && !(ObjectID = GetValueFromNameValueList(&data, "ContainerID")) ) { SoapError(h, 402, "Invalid Args"); goto browse_error; } str.data = malloc(DEFAULT_RESP_SIZE); str.size = DEFAULT_RESP_SIZE; str.off = sprintf(str.data, "%s", resp0); /* See if we need to include DLNA namespace reference */ args.iface = h->iface; args.filter = set_filter_flags(Filter, h); if( args.filter & FILTER_DLNA_NAMESPACE ) ret = strcatf(&str, DLNA_NAMESPACE); if( args.filter & FILTER_PV_SUBTITLE ) ret = strcatf(&str, PV_NAMESPACE); strcatf(&str, ">\n"); args.returned = 0; args.requested = RequestedCount; args.client = h->req_client ? h->req_client->type->type : 0; args.flags = h->req_client ? h->req_client->type->flags : 0; args.str = &str; DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n" " * ObjectID: %s\n" " * Count: %d\n" " * StartingIndex: %d\n" " * BrowseFlag: %s\n" " * Filter: %s\n" " * SortCriteria: %s\n", ObjectID, RequestedCount, StartingIndex, BrowseFlag, Filter, SortCriteria); if( strcmp(BrowseFlag+6, "Metadata") == 0 ) { const char *id = ObjectID; args.requested = 1; magic = in_magic_container(ObjectID, args.flags, &id); if (magic) { if (magic->objectid_sql && strcmp(id, ObjectID) != 0) objectid_sql = magic->objectid_sql; if (magic->parentid_sql && strcmp(id, ObjectID) != 0) parentid_sql = magic->parentid_sql; if (magic->refid_sql) refid_sql = magic->refid_sql; } sql = sqlite3_mprintf("SELECT %s, %s, %s, " COLUMNS "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" " where OBJECT_ID = '%q';", objectid_sql, parentid_sql, refid_sql, id); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); totalMatches = args.returned; } else { magic = check_magic_container(ObjectID, args.flags); if (magic) { if (magic->objectid && *(magic->objectid)) ObjectID = *(magic->objectid); if (magic->objectid_sql) objectid_sql = magic->objectid_sql; if (magic->parentid_sql) parentid_sql = magic->parentid_sql; if (magic->refid_sql) refid_sql = magic->refid_sql; if (magic->where) strncpyt(where, magic->where, sizeof(where)); if (magic->orderby && !GETFLAG(DLNA_STRICT_MASK)) orderBy = strdup(magic->orderby); if (magic->max_count > 0) { int limit = MAX(magic->max_count - StartingIndex, 0); ret = get_child_count(ObjectID, magic); totalMatches = MIN(ret, limit); if (RequestedCount > limit || RequestedCount < 0) RequestedCount = limit; } } if (!where[0]) sqlite3_snprintf(sizeof(where), where, "PARENT_ID = '%q'", ObjectID); if (!totalMatches) totalMatches = get_child_count(ObjectID, magic); ret = 0; if (SortCriteria && !orderBy) { __SORT_LIMIT orderBy = parse_sort_criteria(SortCriteria, &ret); } else if (!orderBy) { if( strncmp(ObjectID, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) { if( strcmp(ObjectID, MUSIC_PLIST_ID) == 0 ) ret = xasprintf(&orderBy, "order by d.TITLE"); else ret = xasprintf(&orderBy, "order by length(OBJECT_ID), OBJECT_ID"); } else if( args.flags & FLAG_FORCE_SORT ) { __SORT_LIMIT ret = xasprintf(&orderBy, "order by o.CLASS, d.DISC, d.TRACK, d.TITLE"); } /* LG TV ordering bug */ else if( args.client == ELGDevice ) ret = xasprintf(&orderBy, "order by o.CLASS, d.TITLE"); else orderBy = parse_sort_criteria(SortCriteria, &ret); if( ret == -1 ) { free(orderBy); orderBy = NULL; ret = 0; } } /* If it's a DLNA client, return an error for bad sort criteria */ if( ret < 0 && ((args.flags & FLAG_DLNA) || GETFLAG(DLNA_STRICT_MASK)) ) { SoapError(h, 709, "Unsupported or invalid sort criteria"); goto browse_error; } sql = sqlite3_mprintf("SELECT %s, %s, %s, " COLUMNS "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" " where %s %s limit %d, %d;", objectid_sql, parentid_sql, refid_sql, where, THISORNUL(orderBy), StartingIndex, RequestedCount); DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); } if( (ret != SQLITE_OK) && (zErrMsg != NULL) ) { DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql); sqlite3_free(zErrMsg); SoapError(h, 709, "Unsupported or invalid sort criteria"); goto browse_error; } sqlite3_free(sql); /* Does the object even exist? */ if( !totalMatches ) { if( !object_exists(ObjectID) ) { SoapError(h, 701, "No such object error"); goto browse_error; } } ret = strcatf(&str, "</DIDL-Lite>\n" "%u\n" "%u\n" "%u" "", args.returned, totalMatches, updateID); BuildSendAndCloseSoapResp(h, str.data, str.off); browse_error: ClearNameValueList(&data); free(orderBy); free(str.data); } static inline void charcat(struct string_s *str, char c) { if (str->size <= str->off) { str->data[str->size-1] = '\0'; return; } str->data[str->off] = c; str->off += 1; } static inline char * parse_search_criteria(const char *str, char *sep) { struct string_s criteria; int len; int literal = 0, like = 0; const char *s; if (!str) return strdup("1 = 1"); len = strlen(str) + 32; criteria.data = malloc(len); criteria.size = len; criteria.off = 0; s = str; while (isspace(*s)) s++; while (*s) { if (literal) { switch (*s) { case '&': if (strncmp(s, """, 6) == 0) s += 5; else if (strncmp(s, "'", 6) == 0) { strcatf(&criteria, "'"); s += 6; continue; } else break; case '"': literal = 0; if (like) { charcat(&criteria, '%'); like--; } charcat(&criteria, '"'); break; case '\\': if (strncmp(s, "\\"", 7) == 0) { strcatf(&criteria, "&quot;"); s += 7; continue; } break; case 'o': if (strncmp(s, "object.", 7) == 0) s += 7; else if (strncmp(s, "object\"", 7) == 0 || strncmp(s, "object"", 12) == 0) { s += 6; continue; } default: charcat(&criteria, *s); break; } } else { switch (*s) { case '\\': if (strncmp(s, "\\"", 7) == 0) { strcatf(&criteria, "&quot;"); s += 7; continue; } else charcat(&criteria, *s); break; case '"': literal = 1; charcat(&criteria, *s); if (like == 2) { charcat(&criteria, '%'); like--; } break; case '&': if (strncmp(s, """, 6) == 0) { literal = 1; strcatf(&criteria, "\""); if (like == 2) { charcat(&criteria, '%'); like--; } s += 5; } else if (strncmp(s, "'", 6) == 0) { strcatf(&criteria, "'"); s += 5; } else if (strncmp(s, "<", 4) == 0) { strcatf(&criteria, "<"); s += 3; } else if (strncmp(s, ">", 4) == 0) { strcatf(&criteria, ">"); s += 3; } else charcat(&criteria, *s); break; case '@': if (strncmp(s, "@refID", 6) == 0) { strcatf(&criteria, "REF_ID"); s += 6; continue; } else if (strncmp(s, "@id", 3) == 0) { strcatf(&criteria, "OBJECT_ID"); s += 3; continue; } else if (strncmp(s, "@parentID", 9) == 0) { strcatf(&criteria, "PARENT_ID"); s += 9; strcpy(sep, "*"); continue; } else charcat(&criteria, *s); break; case 'c': if (strncmp(s, "contains", 8) == 0) { strcatf(&criteria, "like"); s += 8; like = 2; continue; } else charcat(&criteria, *s); break; case 'd': if (strncmp(s, "derivedfrom", 11) == 0) { strcatf(&criteria, "like"); s += 11; like = 1; continue; } else if (strncmp(s, "dc:date", 7) == 0) { strcatf(&criteria, "d.DATE"); s += 7; continue; } else if (strncmp(s, "dc:title", 8) == 0) { strcatf(&criteria, "d.TITLE"); s += 8; continue; } else if (strncmp(s, "dc:creator", 10) == 0) { strcatf(&criteria, "d.CREATOR"); s += 10; continue; } else charcat(&criteria, *s); break; case 'e': if (strncmp(s, "exists", 6) == 0) { s += 6; while (isspace(*s)) s++; if (strncmp(s, "true", 4) == 0) { strcatf(&criteria, "is not NULL"); s += 3; } else if (strncmp(s, "false", 5) == 0) { strcatf(&criteria, "is NULL"); s += 4; } } else charcat(&criteria, *s); break; case 'u': if (strncmp(s, "upnp:class", 10) == 0) { strcatf(&criteria, "o.CLASS"); s += 10; continue; } else if (strncmp(s, "upnp:actor", 10) == 0) { strcatf(&criteria, "d.ARTIST"); s += 10; continue; } else if (strncmp(s, "upnp:artist", 11) == 0) { strcatf(&criteria, "d.ARTIST"); s += 11; continue; } else if (strncmp(s, "upnp:album", 10) == 0) { strcatf(&criteria, "d.ALBUM"); s += 10; continue; } else if (strncmp(s, "upnp:genre", 10) == 0) { strcatf(&criteria, "d.GENRE"); s += 10; continue; } else charcat(&criteria, *s); break; case '(': if (s > str && !isspace(s[-1])) charcat(&criteria, ' '); charcat(&criteria, *s); break; case ')': charcat(&criteria, *s); if (!isspace(s[1])) charcat(&criteria, ' '); break; default: charcat(&criteria, *s); break; } } s++; } charcat(&criteria, '\0'); return criteria.data; } static void SearchContentDirectory(struct upnphttp * h, const char * action) { static const char resp0[] = "" "" "<DIDL-Lite" CONTENT_DIRECTORY_SCHEMAS; struct magic_container_s *magic; char *zErrMsg = NULL; char *sql, *ptr; struct Response args; struct string_s str; int totalMatches; int ret; const char *ContainerID; char *Filter, *SearchCriteria, *SortCriteria; char *orderBy = NULL, *where = NULL, sep[] = "$*"; char groupBy[] = "group by DETAIL_ID"; struct NameValueParserData data; int RequestedCount = 0; int StartingIndex = 0; memset(&args, 0, sizeof(args)); memset(&str, 0, sizeof(str)); ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, 0); ContainerID = GetValueFromNameValueList(&data, "ContainerID"); Filter = GetValueFromNameValueList(&data, "Filter"); SearchCriteria = GetValueFromNameValueList(&data, "SearchCriteria"); SortCriteria = GetValueFromNameValueList(&data, "SortCriteria"); if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) ) RequestedCount = atoi(ptr); if( !RequestedCount ) RequestedCount = -1; if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) ) StartingIndex = atoi(ptr); if( !ContainerID ) { if( !(ContainerID = GetValueFromNameValueList(&data, "ObjectID")) ) { SoapError(h, 402, "Invalid Args"); goto search_error; } } str.data = malloc(DEFAULT_RESP_SIZE); str.size = DEFAULT_RESP_SIZE; str.off = sprintf(str.data, "%s", resp0); /* See if we need to include DLNA namespace reference */ args.iface = h->iface; args.filter = set_filter_flags(Filter, h); if( args.filter & FILTER_DLNA_NAMESPACE ) { ret = strcatf(&str, DLNA_NAMESPACE); } strcatf(&str, ">\n"); args.returned = 0; args.requested = RequestedCount; args.client = h->req_client ? h->req_client->type->type : 0; args.flags = h->req_client ? h->req_client->type->flags : 0; args.str = &str; DPRINTF(E_DEBUG, L_HTTP, "Searching ContentDirectory:\n" " * ObjectID: %s\n" " * Count: %d\n" " * StartingIndex: %d\n" " * SearchCriteria: %s\n" " * Filter: %s\n" " * SortCriteria: %s\n", ContainerID, RequestedCount, StartingIndex, SearchCriteria, Filter, SortCriteria); magic = check_magic_container(ContainerID, args.flags); if (magic && magic->objectid && *(magic->objectid)) ContainerID = *(magic->objectid); if( strcmp(ContainerID, "0") == 0 ) ContainerID = "*"; if( strcmp(ContainerID, MUSIC_ALL_ID) == 0 || GETFLAG(DLNA_STRICT_MASK) ) groupBy[0] = '\0'; where = parse_search_criteria(SearchCriteria, sep); DPRINTF(E_DEBUG, L_HTTP, "Translated SearchCriteria: %s\n", where); totalMatches = sql_get_int_field(db, "SELECT (select count(distinct DETAIL_ID)" " from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)" " where (OBJECT_ID glob '%q%s') and (%s))" " + " "(select count(*) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)" " where (OBJECT_ID = '%q') and (%s))", ContainerID, sep, where, ContainerID, where); if( totalMatches < 0 ) { /* Must be invalid SQL, so most likely bad or unhandled search criteria. */ SoapError(h, 708, "Unsupported or invalid search criteria"); goto search_error; } /* Does the object even exist? */ if( !totalMatches ) { if( !object_exists(ContainerID) ) { SoapError(h, 710, "No such container"); goto search_error; } } ret = 0; __SORT_LIMIT orderBy = parse_sort_criteria(SortCriteria, &ret); /* If it's a DLNA client, return an error for bad sort criteria */ if( ret < 0 && ((args.flags & FLAG_DLNA) || GETFLAG(DLNA_STRICT_MASK)) ) { SoapError(h, 709, "Unsupported or invalid sort criteria"); goto search_error; } sql = sqlite3_mprintf( SELECT_COLUMNS "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" " where OBJECT_ID glob '%q%s' and (%s) %s " "%z %s" " limit %d, %d", ContainerID, sep, where, groupBy, (*ContainerID == '*') ? NULL : sqlite3_mprintf("UNION ALL " SELECT_COLUMNS "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" " where OBJECT_ID = '%q' and (%s) ", ContainerID, where), orderBy, StartingIndex, RequestedCount); DPRINTF(E_DEBUG, L_HTTP, "Search SQL: %s\n", sql); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); if( (ret != SQLITE_OK) && (zErrMsg != NULL) ) { DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql); sqlite3_free(zErrMsg); } sqlite3_free(sql); ret = strcatf(&str, "</DIDL-Lite>\n" "%u\n" "%u\n" "%u" "", args.returned, totalMatches, updateID); BuildSendAndCloseSoapResp(h, str.data, str.off); search_error: ClearNameValueList(&data); free(orderBy); free(where); free(str.data); } /* If a control point calls QueryStateVariable on a state variable that is not buffered in memory within (or otherwise available from) the service, the service must return a SOAP fault with an errorCode of 404 Invalid Var. QueryStateVariable remains useful as a limited test tool but may not be part of some future versions of UPnP. */ static void QueryStateVariable(struct upnphttp * h, const char * action) { static const char resp[] = "" "%s" ""; char body[512]; struct NameValueParserData data; const char * var_name; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, 0); /*var_name = GetValueFromNameValueList(&data, "QueryStateVariable"); */ /*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/ var_name = GetValueFromNameValueList(&data, "varName"); DPRINTF(E_INFO, L_HTTP, "QueryStateVariable(%.40s)\n", var_name); if(!var_name) { SoapError(h, 402, "Invalid Args"); } else if(strcmp(var_name, "ConnectionStatus") == 0) { int bodylen; bodylen = snprintf(body, sizeof(body), resp, action, "urn:schemas-upnp-org:control-1-0", "Connected", action); BuildSendAndCloseSoapResp(h, body, bodylen); } else { DPRINTF(E_WARN, L_HTTP, "%s: Unknown: %s\n", action, THISORNUL(var_name)); SoapError(h, 404, "Invalid Var"); } ClearNameValueList(&data); } static void SamsungGetFeatureList(struct upnphttp * h, const char * action) { static const char resp[] = "" "" "<Features xmlns=\"urn:schemas-upnp-org:av:avs\"" " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" " xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\">" "<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">" "<container id=\"%s\" type=\"object.item.audioItem\"/>" "<container id=\"%s\" type=\"object.item.videoItem\"/>" "<container id=\"%s\" type=\"object.item.imageItem\"/>" "</Feature>" "</Features>" ""; const char *audio = MUSIC_ID; const char *video = VIDEO_ID; const char *image = IMAGE_ID; char body[1024]; int len; if (runtime_vars.root_container) { if (strcmp(runtime_vars.root_container, BROWSEDIR_ID) == 0) { audio = MUSIC_DIR_ID; video = VIDEO_DIR_ID; image = IMAGE_DIR_ID; } else { audio = runtime_vars.root_container; video = runtime_vars.root_container; image = runtime_vars.root_container; } } len = snprintf(body, sizeof(body), resp, audio, video, image); BuildSendAndCloseSoapResp(h, body, len); } static void SamsungSetBookmark(struct upnphttp * h, const char * action) { static const char resp[] = "" ""; struct NameValueParserData data; char *ObjectID, *PosSecond; ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, 0); ObjectID = GetValueFromNameValueList(&data, "ObjectID"); PosSecond = GetValueFromNameValueList(&data, "PosSecond"); if( ObjectID && PosSecond ) { int ret; ret = sql_exec(db, "INSERT OR REPLACE into BOOKMARKS" " VALUES " "((select DETAIL_ID from OBJECTS where OBJECT_ID = '%q'), %q)", ObjectID, PosSecond); if( ret != SQLITE_OK ) DPRINTF(E_WARN, L_METADATA, "Error setting bookmark %s on ObjectID='%s'\n", PosSecond, ObjectID); BuildSendAndCloseSoapResp(h, resp, sizeof(resp)-1); } else SoapError(h, 402, "Invalid Args"); ClearNameValueList(&data); } static const struct { const char * methodName; void (*methodImpl)(struct upnphttp *, const char *); } soapMethods[] = { { "QueryStateVariable", QueryStateVariable}, { "Browse", BrowseContentDirectory}, { "Search", SearchContentDirectory}, { "GetSearchCapabilities", GetSearchCapabilities}, { "GetSortCapabilities", GetSortCapabilities}, { "GetSystemUpdateID", GetSystemUpdateID}, { "GetProtocolInfo", GetProtocolInfo}, { "GetCurrentConnectionIDs", GetCurrentConnectionIDs}, { "GetCurrentConnectionInfo", GetCurrentConnectionInfo}, { "IsAuthorized", IsAuthorizedValidated}, { "IsValidated", IsAuthorizedValidated}, { "RegisterDevice", RegisterDevice}, { "X_GetFeatureList", SamsungGetFeatureList}, { "X_SetBookmark", SamsungSetBookmark}, { 0, 0 } }; void ExecuteSoapAction(struct upnphttp * h, const char * action, int n) { char * p; p = strchr(action, '#'); if(p) { int i = 0; int len; int methodlen; char * p2; p++; p2 = strchr(p, '"'); if(p2) methodlen = p2 - p; else methodlen = n - (p - action); DPRINTF(E_DEBUG, L_HTTP, "SoapMethod: %.*s\n", methodlen, p); while(soapMethods[i].methodName) { len = strlen(soapMethods[i].methodName); if(strncmp(p, soapMethods[i].methodName, len) == 0) { soapMethods[i].methodImpl(h, soapMethods[i].methodName); return; } i++; } DPRINTF(E_WARN, L_HTTP, "SoapMethod: Unknown: %.*s\n", methodlen, p); } SoapError(h, 401, "Invalid Action"); } minidlna-1.1.5+dfsg/upnpsoap.h000066400000000000000000000027541261774340000162620ustar00rootroot00000000000000/* MiniDLNA project * http://minidlna.sourceforge.net/ * * MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #ifndef __UPNPSOAP_H__ #define __UPNPSOAP_H__ #define DEFAULT_RESP_SIZE 131072 #define MAX_RESPONSE_SIZE 2097152 #define CONTENT_DIRECTORY_SCHEMAS \ " xmlns:dc=\"http://purl.org/dc/elements/1.1/\"" \ " xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\"" \ " xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\"" #define DLNA_NAMESPACE \ " xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"" #define PV_NAMESPACE \ " xmlns:pv=\"http://www.pv.com/pvns/\"" struct Response { struct string_s *str; int start; int returned; int requested; int iface; uint32_t filter; uint32_t flags; enum client_types client; }; /* ExecuteSoapAction(): * this method executes the requested Soap Action */ void ExecuteSoapAction(struct upnphttp *, const char *, int); #endif minidlna-1.1.5+dfsg/utils.c000066400000000000000000000247741261774340000155560ustar00rootroot00000000000000/* MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "minidlnatypes.h" #include "upnpglobalvars.h" #include "utils.h" #include "log.h" int xasprintf(char **strp, char *fmt, ...) { va_list args; int ret; va_start(args, fmt); ret = vasprintf(strp, fmt, args); va_end(args); if( ret < 0 ) { DPRINTF(E_WARN, L_GENERAL, "xasprintf: allocation failed\n"); *strp = NULL; } return ret; } int ends_with(const char * haystack, const char * needle) { const char * end; int nlen = strlen(needle); int hlen = strlen(haystack); if( nlen > hlen ) return 0; end = haystack + hlen - nlen; return (strcasecmp(end, needle) ? 0 : 1); } char * trim(char *str) { int i; int len; if (!str) return(NULL); len = strlen(str); for (i=len-1; i >= 0 && isspace(str[i]); i--) { str[i] = '\0'; len--; } while (isspace(*str)) { str++; len--; } if (str[0] == '"' && str[len-1] == '"') { str[0] = '\0'; str[len-1] = '\0'; str++; } return str; } /* Find the first occurrence of p in s, where s is terminated by t */ char * strstrc(const char *s, const char *p, const char t) { char *endptr; size_t slen, plen; endptr = strchr(s, t); if (!endptr) return strstr(s, p); plen = strlen(p); slen = endptr - s; while (slen >= plen) { if (*s == *p && strncmp(s+1, p+1, plen-1) == 0) return (char*)s; s++; slen--; } return NULL; } char * strcasestrc(const char *s, const char *p, const char t) { char *endptr; size_t slen, plen; endptr = strchr(s, t); if (!endptr) return strcasestr(s, p); plen = strlen(p); slen = endptr - s; while (slen >= plen) { if (*s == *p && strncasecmp(s+1, p+1, plen-1) == 0) return (char*)s; s++; slen--; } return NULL; } char * modifyString(char *string, const char *before, const char *after, int noalloc) { int oldlen, newlen, chgcnt = 0; char *s, *p; /* If there is no match, just return */ s = strstr(string, before); if (!s) return string; oldlen = strlen(before); newlen = strlen(after); if (newlen > oldlen) { if (noalloc) return string; while ((p = strstr(s, before))) { chgcnt++; s = p + oldlen; } s = realloc(string, strlen(string)+((newlen-oldlen)*chgcnt)+1); /* If we failed to realloc, return the original alloc'd string */ if( s ) string = s; else return string; } s = string; while (s) { p = strstr(s, before); if (!p) return string; memmove(p + newlen, p + oldlen, strlen(p + oldlen) + 1); memcpy(p, after, newlen); s = p + newlen; } return string; } char * unescape_tag(const char *tag, int force_alloc) { char *esc_tag = NULL; if (strchr(tag, '&') && (strstr(tag, "&") || strstr(tag, "<") || strstr(tag, ">") || strstr(tag, """) || strstr(tag, "'"))) { esc_tag = strdup(tag); esc_tag = modifyString(esc_tag, "&", "&", 1); esc_tag = modifyString(esc_tag, "<", "<", 1); esc_tag = modifyString(esc_tag, ">", ">", 1); esc_tag = modifyString(esc_tag, """, "\"", 1); esc_tag = modifyString(esc_tag, "'", "'", 1); } else if( force_alloc ) esc_tag = strdup(tag); return esc_tag; } char * escape_tag(const char *tag, int force_alloc) { char *esc_tag = NULL; if( strchr(tag, '&') || strchr(tag, '<') || strchr(tag, '>') || strchr(tag, '"') ) { esc_tag = strdup(tag); esc_tag = modifyString(esc_tag, "&", "&amp;", 0); esc_tag = modifyString(esc_tag, "<", "&lt;", 0); esc_tag = modifyString(esc_tag, ">", "&gt;", 0); esc_tag = modifyString(esc_tag, "\"", "&quot;", 0); } else if( force_alloc ) esc_tag = strdup(tag); return esc_tag; } char * strip_ext(char *name) { char *period; period = strrchr(name, '.'); if (period) *period = '\0'; return period; } /* Code basically stolen from busybox */ int make_dir(char * path, mode_t mode) { char * s = path; char c; struct stat st; do { c = '\0'; /* Before we do anything, skip leading /'s, so we don't bother * trying to create /. */ while (*s == '/') ++s; /* Bypass leading non-'/'s and then subsequent '/'s. */ while (*s) { if (*s == '/') { do { ++s; } while (*s == '/'); c = *s; /* Save the current char */ *s = '\0'; /* and replace it with nul. */ break; } ++s; } if (mkdir(path, mode) < 0) { /* If we failed for any other reason than the directory * already exists, output a diagnostic and return -1.*/ if ((errno != EEXIST && errno != EISDIR) || (stat(path, &st) < 0 || !S_ISDIR(st.st_mode))) { DPRINTF(E_WARN, L_GENERAL, "make_dir: cannot create directory '%s'\n", path); if (c) *s = c; return -1; } } if (!c) return 0; /* Remove any inserted nul from the path. */ *s = c; } while (1); } /* Simple, efficient hash function from Daniel J. Bernstein */ unsigned int DJBHash(uint8_t *data, int len) { unsigned int hash = 5381; unsigned int i = 0; for(i = 0; i < len; data++, i++) { hash = ((hash << 5) + hash) + (*data); } return hash; } const char * mime_to_ext(const char * mime) { switch( *mime ) { /* Audio extensions */ case 'a': if( strcmp(mime+6, "mpeg") == 0 ) return "mp3"; else if( strcmp(mime+6, "mp4") == 0 ) return "m4a"; else if( strcmp(mime+6, "x-ms-wma") == 0 ) return "wma"; else if( strcmp(mime+6, "x-flac") == 0 ) return "flac"; else if( strcmp(mime+6, "flac") == 0 ) return "flac"; else if( strcmp(mime+6, "x-wav") == 0 ) return "wav"; else if( strncmp(mime+6, "L16", 3) == 0 ) return "pcm"; else if( strcmp(mime+6, "3gpp") == 0 ) return "3gp"; else if( strcmp(mime, "application/ogg") == 0 ) return "ogg"; break; case 'v': if( strcmp(mime+6, "avi") == 0 ) return "avi"; else if( strcmp(mime+6, "divx") == 0 ) return "avi"; else if( strcmp(mime+6, "x-msvideo") == 0 ) return "avi"; else if( strcmp(mime+6, "mpeg") == 0 ) return "mpg"; else if( strcmp(mime+6, "mp4") == 0 ) return "mp4"; else if( strcmp(mime+6, "x-ms-wmv") == 0 ) return "wmv"; else if( strcmp(mime+6, "x-matroska") == 0 ) return "mkv"; else if( strcmp(mime+6, "x-mkv") == 0 ) return "mkv"; else if( strcmp(mime+6, "x-flv") == 0 ) return "flv"; else if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 ) return "mpg"; else if( strcmp(mime+6, "quicktime") == 0 ) return "mov"; else if( strcmp(mime+6, "3gpp") == 0 ) return "3gp"; else if( strncmp(mime+6, "x-tivo-mpeg", 11) == 0 ) return "TiVo"; break; case 'i': if( strcmp(mime+6, "jpeg") == 0 ) return "jpg"; else if( strcmp(mime+6, "png") == 0 ) return "png"; break; default: break; } return "dat"; } int is_video(const char * file) { return (ends_with(file, ".mpg") || ends_with(file, ".mpeg") || ends_with(file, ".avi") || ends_with(file, ".divx") || ends_with(file, ".asf") || ends_with(file, ".wmv") || ends_with(file, ".mp4") || ends_with(file, ".m4v") || ends_with(file, ".mts") || ends_with(file, ".m2ts") || ends_with(file, ".m2t") || ends_with(file, ".mkv") || ends_with(file, ".vob") || ends_with(file, ".ts") || ends_with(file, ".flv") || ends_with(file, ".xvid") || #ifdef TIVO_SUPPORT ends_with(file, ".TiVo") || #endif ends_with(file, ".mov") || ends_with(file, ".3gp")); } int is_audio(const char * file) { return (ends_with(file, ".mp3") || ends_with(file, ".flac") || ends_with(file, ".wma") || ends_with(file, ".asf") || ends_with(file, ".fla") || ends_with(file, ".flc") || ends_with(file, ".m4a") || ends_with(file, ".aac") || ends_with(file, ".mp4") || ends_with(file, ".m4p") || ends_with(file, ".wav") || ends_with(file, ".ogg") || ends_with(file, ".pcm") || ends_with(file, ".3gp")); } int is_image(const char * file) { return (ends_with(file, ".jpg") || ends_with(file, ".jpeg")); } int is_playlist(const char * file) { return (ends_with(file, ".m3u") || ends_with(file, ".pls")); } int is_caption(const char * file) { return (ends_with(file, ".srt") || ends_with(file, ".smi")); } int is_album_art(const char * name) { struct album_art_name_s * album_art_name; /* Check if this file name matches one of the default album art names */ for( album_art_name = album_art_names; album_art_name; album_art_name = album_art_name->next ) { if( album_art_name->wildcard ) { if( strncmp(album_art_name->name, name, strlen(album_art_name->name)) == 0 ) break; } else { if( strcmp(album_art_name->name, name) == 0 ) break; } } return (album_art_name ? 1 : 0); } int resolve_unknown_type(const char * path, media_types dir_type) { struct stat entry; unsigned char type = TYPE_UNKNOWN; char str_buf[PATH_MAX]; ssize_t len; if( lstat(path, &entry) == 0 ) { if( S_ISLNK(entry.st_mode) ) { if( (len = readlink(path, str_buf, PATH_MAX-1)) > 0 ) { str_buf[len] = '\0'; //DEBUG DPRINTF(E_DEBUG, L_GENERAL, "Checking for recursive symbolic link: %s (%s)\n", path, str_buf); if( strncmp(path, str_buf, strlen(str_buf)) == 0 ) { DPRINTF(E_DEBUG, L_GENERAL, "Ignoring recursive symbolic link: %s (%s)\n", path, str_buf); return type; } } stat(path, &entry); } if( S_ISDIR(entry.st_mode) ) { type = TYPE_DIR; } else if( S_ISREG(entry.st_mode) ) { switch( dir_type ) { case ALL_MEDIA: if( is_image(path) || is_audio(path) || is_video(path) || is_playlist(path) ) type = TYPE_FILE; break; case TYPE_AUDIO: if( is_audio(path) || is_playlist(path) ) type = TYPE_FILE; break; case TYPE_VIDEO: if( is_video(path) ) type = TYPE_FILE; break; case TYPE_IMAGES: if( is_image(path) ) type = TYPE_FILE; break; default: break; } } } return type; } minidlna-1.1.5+dfsg/utils.h000066400000000000000000000053201261774340000155450ustar00rootroot00000000000000/* Utility functions * * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Justin Maggard * * MiniDLNA media server * Copyright (C) 2008-2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #ifndef __UTILS_H__ #define __UTILS_H__ #include #include #include #include "minidlnatypes.h" /* String functions */ /* We really want this one inlined, since it has a major performance impact */ static inline int __attribute__((__format__ (__printf__, 2, 3))) strcatf(struct string_s *str, const char *fmt, ...) { int ret; int size; va_list ap; if (str->off >= str->size) return 0; va_start(ap, fmt); size = str->size - str->off; ret = vsnprintf(str->data + str->off, size, fmt, ap); str->off += MIN(ret, size); va_end(ap); return ret; } static inline void strncpyt(char *dst, const char *src, size_t len) { strncpy(dst, src, len); dst[len-1] = '\0'; } static inline int is_reg(const struct dirent *d) { #if HAVE_STRUCT_DIRENT_D_TYPE return (d->d_type == DT_REG); #else return -1; #endif } static inline int is_dir(const struct dirent *d) { #if HAVE_STRUCT_DIRENT_D_TYPE return (d->d_type == DT_DIR); #else return -1; #endif } int xasprintf(char **strp, char *fmt, ...) __attribute__((__format__ (__printf__, 2, 3))); int ends_with(const char * haystack, const char * needle); char *trim(char *str); char *strstrc(const char *s, const char *p, const char t); char *strcasestrc(const char *s, const char *p, const char t); char *modifyString(char *string, const char *before, const char *after, int noalloc); char *escape_tag(const char *tag, int force_alloc); char *unescape_tag(const char *tag, int force_alloc); char *strip_ext(char *name); /* Metadata functions */ int is_video(const char * file); int is_audio(const char * file); int is_image(const char * file); int is_playlist(const char * file); int is_caption(const char * file); int is_album_art(const char * name); int resolve_unknown_type(const char * path, media_types dir_type); const char *mime_to_ext(const char * mime); /* Others */ int make_dir(char * path, mode_t mode); unsigned int DJBHash(uint8_t *data, int len); #endif minidlna-1.1.5+dfsg/uuid.c000066400000000000000000000143051261774340000153510ustar00rootroot00000000000000/* MiniDLNA project * * http://sourceforge.net/projects/minidlna/ * * Much of this code and ideas for this code have been taken * from Helge Deller's proposed Linux kernel patch (which * apparently never made it upstream), and some from Busybox. * * MiniDLNA media server * Copyright (C) 2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #if HAVE_MACH_MACH_TIME_H #include #elif HAVE_CLOCK_GETTIME_SYSCALL #include #endif #include "uuid.h" #include "getifaddr.h" #include "log.h" static uint32_t clock_seq; static const uint32_t clock_seq_max = 0x3fff; /* 14 bits */ static int clock_seq_initialized; #ifndef CLOCK_MONOTONIC #define CLOCK_MONOTONIC CLOCK_REALTIME #endif unsigned long long monotonic_us(void) { struct timespec ts; #if HAVE_CLOCK_GETTIME clock_gettime(CLOCK_MONOTONIC, &ts); #elif HAVE_CLOCK_GETTIME_SYSCALL syscall(__NR_clock_gettime, CLOCK_MONOTONIC, &ts); #elif HAVE_MACH_MACH_TIME_H return mach_absolute_time(); #else struct timeval tv; gettimeofday(&tv, 0); TIMEVAL_TO_TIMESPEC(&tv, &ts); #endif return ts.tv_sec * 1000000ULL + ts.tv_nsec / 1000; } int read_bootid_node(unsigned char *buf, size_t size) { FILE *boot_id; if(size != 6) return -1; boot_id = fopen("/proc/sys/kernel/random/boot_id", "r"); if(!boot_id) return -1; if((fseek(boot_id, 24, SEEK_SET) < 0) || (fscanf(boot_id, "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", &buf[0], &buf[1], &buf[2], &buf[3], &buf[4], &buf[5]) != 6)) { fclose(boot_id); return -1; } fclose(boot_id); return 0; } static void read_random_bytes(unsigned char *buf, size_t size) { int i; pid_t pid; i = open("/dev/urandom", O_RDONLY); if(i >= 0) { if (read(i, buf, size) == -1) DPRINTF(E_MAXDEBUG, L_GENERAL, "Failed to read random bytes\n"); close(i); } /* Paranoia. /dev/urandom may be missing. * rand() is guaranteed to generate at least [0, 2^15) range, * but lowest bits in some libc are not so "random". */ srand(monotonic_us()); pid = getpid(); while(1) { for(i = 0; i < size; i++) buf[i] ^= rand() >> 5; if(pid == 0) break; srand(pid); pid = 0; } } void init_clockseq(void) { unsigned char buf[4]; read_random_bytes(buf, 4); memcpy(&clock_seq, &buf, sizeof(clock_seq)); clock_seq &= clock_seq_max; clock_seq_initialized = 1; } int generate_uuid(unsigned char uuid_out[16]) { static uint64_t last_time_all; static unsigned int clock_seq_started; static char last_node[6] = { 0, 0, 0, 0, 0, 0 }; struct timespec ts; uint64_t time_all; int inc_clock_seq = 0; unsigned char mac[6]; int mac_error; memset(&mac, '\0', sizeof(mac)); /* Get the spatially unique node identifier */ mac_error = getsyshwaddr((char *)mac, sizeof(mac)); if(!mac_error) { memcpy(&uuid_out[10], mac, ETH_ALEN); } else { /* use bootid's nodeID if no network interface found */ DPRINTF(E_INFO, L_HTTP, "Could not find MAC. Use bootid's nodeID.\n"); if( read_bootid_node(&uuid_out[10], 6) != 0) { DPRINTF(E_INFO, L_HTTP, "bootid node not successfully read.\n"); read_random_bytes(&uuid_out[10], 6); } } if(memcmp(last_node, uuid_out+10, 6) != 0) { inc_clock_seq = 1; memcpy(last_node, uuid_out+10, 6); } /* Determine 60-bit timestamp value. For UUID version 1, this is * represented by Coordinated Universal Time (UTC) as a count of 100- * nanosecond intervals since 00:00:00.00, 15 October 1582 (the date of * Gregorian reform to the Christian calendar). */ #if HAVE_CLOCK_GETTIME clock_gettime(CLOCK_REALTIME, &ts); #elif HAVE_CLOCK_GETTIME_SYSCALL syscall(__NR_clock_gettime, CLOCK_REALTIME, &ts); #else struct timeval tv; gettimeofday(&tv, 0); TIMEVAL_TO_TIMESPEC(&tv, &ts); #endif time_all = ((uint64_t)ts.tv_sec) * (NSEC_PER_SEC / 100); time_all += ts.tv_nsec / 100; /* add offset from Gregorian Calendar to Jan 1 1970 */ time_all += 12219292800000ULL * (NSEC_PER_MSEC / 100); time_all &= 0x0fffffffffffffffULL; /* limit to 60 bits */ /* Determine clock sequence (max. 14 bit) */ if(!clock_seq_initialized) { init_clockseq(); clock_seq_started = clock_seq; } else { if(inc_clock_seq || time_all <= last_time_all) { clock_seq = (clock_seq + 1) & clock_seq_max; if(clock_seq == clock_seq_started) { clock_seq = (clock_seq - 1) & clock_seq_max; } } else clock_seq_started = clock_seq; } last_time_all = time_all; /* Fill in timestamp and clock_seq values */ uuid_out[3] = (uint8_t)time_all; uuid_out[2] = (uint8_t)(time_all >> 8); uuid_out[1] = (uint8_t)(time_all >> 16); uuid_out[0] = (uint8_t)(time_all >> 24); uuid_out[5] = (uint8_t)(time_all >> 32); uuid_out[4] = (uint8_t)(time_all >> 40); uuid_out[7] = (uint8_t)(time_all >> 48); uuid_out[6] = (uint8_t)(time_all >> 56); uuid_out[8] = clock_seq >> 8; uuid_out[9] = clock_seq & 0xff; /* Set UUID version to 1 --- time-based generation */ uuid_out[6] = (uuid_out[6] & 0x0F) | 0x10; /* Set the UUID variant to DCE */ uuid_out[8] = (uuid_out[8] & 0x3F) | 0x80; return 0; } /* Places a null-terminated 37-byte time-based UUID string in the buffer pointer to by buf. * A large enough buffer must already be allocated. */ int get_uuid_string(char *buf) { unsigned char uuid[16]; if( generate_uuid(uuid) != 0 ) return -1; sprintf(buf, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]); buf[36] = '\0'; return 0; } minidlna-1.1.5+dfsg/uuid.h000066400000000000000000000020131261774340000153470ustar00rootroot00000000000000/* UUID generation functions * * Project : minidlna * Website : http://sourceforge.net/projects/minidlna/ * Author : Justin Maggard * * MiniDLNA media server * Copyright (C) 2009 Justin Maggard * * This file is part of MiniDLNA. * * MiniDLNA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * MiniDLNA 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 MiniDLNA. If not, see . */ #ifndef __UUID_H__ #define __UUID_H__ #define ETH_ALEN 6 #ifndef NSEC_PER_SEC #define NSEC_PER_SEC 1000000000L #endif #ifndef NSEC_PER_MSEC #define NSEC_PER_MSEC 1000000L #endif int get_uuid_string(char *buf); #endif