diff --git a/Makefile.am b/Makefile.am index 588149ae..99025d4e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,3 +3,23 @@ AUTOMAKE_OPTIONS = dist-bzip2 no-dist-gzip SUBDIRS = test src +lms_docrootdir=$(pkgdatadir)/docroot +lms_approotdir=$(pkgdatadir)/approot + +lms_cssdir=$(lms_docrootdir)/css +lms_imagesdir=$(lms_docrootdir)/images + +lms_css_DATA = \ + docroot/css/lms.css + +lms_images_DATA = \ + docroot/images/unknown-cover.jpg + +lms_approot_DATA = \ + approot/templates.xml + +install-data-hook: + ln -s $(datadir)/Wt/resources $(lms_docrootdir)/resources || echo "Skipped symlink creation" + +uninstall-local: + cd $(lms_docrootdir) && rm -f -r $(lms_docrootdir)/resources diff --git a/README.md b/README.md index 301cb990..2ecaa872 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ It allows you to access your music and your videos using an https web interface. - Winamp-like interface, suited for large databases - Audio/Video transcode for maximum interoperability and low bandwith requirements - User management - - LMS API available in Protocol Buffers (see src/lms-api/proto/messages.proto) Please note LMS is still under development and will gain more features in the future. @@ -17,63 +16,51 @@ Please note some media files may require significant CPU usage to be transcoded. ## Dependencies ### Debian or Ubuntu packages ```sh -$ apt-get install g++ autoconf automake libboost-dev libboost-locale-dev libboost-iostreams-dev libboost-log-dev libavcodec-dev libwtdbosqlite-dev libwthttp-dev libwtdbo-dev libwt-dev libconfig++-dev libjpeg8-dev libavcodec-dev libavformat-dev libavutil-dvegi libav-tools +$ apt-get install g++ autoconf automake libboost-dev libboost-locale-dev libboost-iostreams-dev libboost-log-dev libavcodec-dev libwtdbosqlite-dev libwthttp-dev libwtdbo-dev libwt-dev libmagick++-dev libavcodec-dev libavformat-dev libavutil ``` -If you want to enable the LMS API, you need these additional packages: - -```sh -$ apt-get install libprotobuf-dev libprotobuf-dev protobuf-compiler -``` - -Warning: your distrib may provide an outdated wt library. Using the latest Wt (>= 3.3.3) is highly recommended (see http://www.webtoolkit.eu/wt) - ## Build ```sh $ autoreconf -vfi $ mkdir build $ cd build -$ ../configure +$ ../configure --prefix=/usr ``` - configure will complain if a mandatory library is missing. -The configure script allows you to disable the LMS API support, see 'configure --help' for more information. ```sh -$ make +$ make -j 4 ``` -## Setting up SSL materials -This is required by the SSL server and the web server. +## Install +```sh +$ make install +``` +This command requires root privileges +## Running +```sh +$ /usr/bin/lms --docroot='/usr/share/lms/docroot/;/resources,/css,/images' --approot=/usr/share/lms/approot --http-address 0.0.0.0 --http-port 5081 +``` +It is highly recommended to run LMS as a non root user. +The exectuable needs write accesses to the /var/lms/ directory. + +## Setting up SSL materials (optional) Here is just a self signed certificate example, you could do use a CA if you want. Generate a dh file: ```sh $ openssl dhparam -out dh2048.pem 2048 ``` - Generate a self signed certificate: ```sh $ openssl req -x509 -out cert.pem -keyout privkey.pem -newkey rsa:4096 ``` -## Running - -In the build directory: ```sh -$ ./src/lms lms.conf +$ /usr/bin/lms --docroot='/usr/share/lms/docroot/;/resources,/css,/images' --approot=/usr/share/lms/approot --https-address 0.0.0.0 --https-port 5081 --ssl-certificate cert.pem --ssl-private-key privkey.pem --ssl-tmp-dh dh2048.pem ``` - -Create the lms.conf file using the etc/lms.sample.conf - - "ui.resources.approot" has to refer to the ui/approot/ directory. - - "ui.resources.docroot" has to refer to the ui/docroot/ directory -You must also link Wt's docroot (usually located in /usr/share/Wt) into that directory. Example: -```sh -ln -s /var/lms/docroot/resources /usr/share/Wt/resources -``` - Depending on your SSL parameters, you may be asked for the PEM passphrase to unlock the private key. ## Credits @@ -81,5 +68,4 @@ Depending on your SSL parameters, you may be asked for the PEM passphrase to unl - Wt (http://www.webtoolkit.eu/) - bootstrap3 (http://getbootstrap.com/) - libav project (https://www.libav.org/). -- Protocol Buffers (https://github.com/google/protobuf/) 73,1 Bot diff --git a/TODO b/TODO index 01b7e987..4994d6a7 100644 --- a/TODO +++ b/TODO @@ -3,8 +3,6 @@ - Make the feature [Cover] -- Scaling: find something more "reliable" than GIL and its custom extensions (adobe work, io_new)? -- Handle several file formats (not only jpg), use libmagic? - Handle preferred cover file names - Implement a cache and a grabber from some web service (mandatory for artists) @@ -25,18 +23,12 @@ [Users] - Handle login lifetime? -- Limit multi login from UI and remote interfaces (limit per interface is acceptable) - -[ServiceManager] -- Rework the whole start/stop/try/cach/thread/interrupts things -- Use our own WIOService +- Use the WServer::post method to kick users already logged in? +- Limit multi login from UI and API interfaces (limit per interface is acceptable) [Services] - consider adding support for external web servers (new FCGI service?) -[Transcode] -- some early end of playback spotted on files. Maybe this is because the stderr of the forked process is not properly closed? - [UI] - handle internationalization [Settings] @@ -74,12 +66,8 @@ - Make a dedicated audio layout in mobile environment (keep the same layout for videos?) - Handle internal path -[Lms API] -- Implement partial text search options in the GetXXX messages -- Make it REST ? - [REST API] -- Make another dedicated REST API. Maybe use the SubSonic API or Ampache API? +- Make a dedicated REST API. Maybe use the SubSonic API or Ampache API? [Logs] -- Capture transcoder log output to get more information on errors +- Capture transcoder log output to get more information on errors? diff --git a/src/ui/approot/templates.xml b/approot/templates.xml similarity index 100% rename from src/ui/approot/templates.xml rename to approot/templates.xml diff --git a/conf/lms.conf.sample b/conf/lms.conf.sample deleted file mode 100644 index 01ffec79..00000000 --- a/conf/lms.conf.sample +++ /dev/null @@ -1,63 +0,0 @@ - -main = { - - logger = { - level = 7; # level common for all loggers - file = { - enable = true; - path = "/var/lms/lms.log"; # comment to disable file logging - } - console = { - enable = true; - } - } - - database = { - path = "/var/lms/lms.db"; - - audio_extensions = "mp3 ogg oga aac m4a flac wav wma aif aiff ape mpc shn"; - video_extensions = "flv avi mpg mpeg mp4 m4v mkv mov wmv ogv divx m2ts"; - } - - cover = { - file_extensions = "jpg jpeg"; - file_max_size = 5000000; - file_preferred_names = "cover front"; - } -} - - -ui = { - resources = { - docroot = "/var/lms/docroot" - approot = "/var/lms/approot" - } - - listen-endpoint = { - port = 5081; - addr = "0.0.0.0"; - } - - ssl-crypto = { - cert = "/var/lms/certs/certUI.pem"; - key = "/var/lms/private/privkeyUI.pem"; - dh = "/var/lms/dh/dh2048.pem"; - } - -} - -remote = { - nb-threads = 1; - - listen-endpoint = { - port = 5080; - addr = "0.0.0.0"; - } - - ssl-crypto = { - cert = "/var/lms/certs/certRemote.pem"; - key = "/var/lms/private/privkeyRemote.pem"; - dh = "/var/lms/dh/dh2048.pem"; - } -} - diff --git a/configure.ac b/configure.ac index e7408b53..c7af1f14 100644 --- a/configure.ac +++ b/configure.ac @@ -8,56 +8,26 @@ AC_LANG_CPLUSPLUS # Checks for programs AC_PROG_CXX +AC_ARG_ENABLE([video], AS_HELP_STRING([--enable-video], + [Enable Video support. Default to no.])], + [enable_video="$enableval"], + [enable_video="no"]) -AC_ARG_ENABLE(lmsapi, AS_HELP_STRING([--enable-lmsapi], - [Build with the lmsapi protocol support. Defaults to yes.])], - [enable_lmsapi="$enableval"], - [enable_lmsapi="yes"]) +AM_CONDITIONAL([VIDEO], [test x$enable_video = xyes]) +AS_IF([test x$enable_video = xyes], [AC_DEFINE(HAVE_VIDEO, [1], [Enable Video support])]) -# ProtoBuf compiler. -# First, specify with --with-protoc=/path/of/protoc. -# Or, specify with env variable PROTOC. -# If neither of the above, find it in the path. -AS_IF([test "$enable_lmsapi" = "yes"], - [AC_ARG_WITH([protoc], - [AS_HELP_STRING([--with-protoc=/path/of/protoc], - [Location of the protocol buffers compiler protoc. Defaults to looking on path.])], - [PROTOC="$withval"], - [ AS_IF([test "x${PROTOC}" == "x"], - [AC_PATH_PROG([PROTOC], [protoc], [no])])] - ) - ] - [ AS_IF([test "${PROTOC}" == "no"], [AC_MSG_ERROR([ProtoBuf compiler "protoc" not found.])]) ] - [AC_CHECK_LIB( [protobuf], - [main], - , - [AC_MSG_ERROR([libprotobuf not found!])])] - [lmsapi=true] - , - [lmsapi=false]) +PKG_CHECK_MODULES(IMAGEMAGICKXX, "ImageMagick++", [ HAVE_IMAGEMAGICKXX=yes ], [ ]) +if test -n "$HAVE_IMAGEMAGICKXX"; then + MAGICKXX_CFLAGS="$IMAGEMAGICKXX_CFLAGS" + MAGICKXX_LIBS="$IMAGEMAGICKXX_LIBS" +fi +AC_SUBST(MAGICKXX_CFLAGS) +AC_SUBST(MAGICKXX_LIBS) -AM_CONDITIONAL([LMSAPI], [test x$lmsapi = xtrue]) -AS_IF([test x$lmsapi = xtrue], [AC_DEFINE(HAVE_LMSAPI, [1], [Enable LMS API support])]) - -AC_CHECK_HEADERS([Wt/WApplication jpeglib.h], +AC_CHECK_HEADERS([Wt/WApplication], [], [AC_MSG_ERROR([Header not found or unusable !])]) -AC_CHECK_LIB([config++], - [main], - , - [AC_MSG_ERROR([config++ not found!])]) - -AC_CHECK_LIB([jpeg], - [jpeg_destroy], - , - [AC_MSG_ERROR([libpthread not found!])]) - -AC_CHECK_LIB([pthread], - [pthread_create], - , - [AC_MSG_ERROR([libpthread not found!])]) - AC_CHECK_LIB([avutil], [av_free], , @@ -73,16 +43,6 @@ AC_CHECK_LIB([avformat], , [AC_MSG_ERROR([libavformat not found!])]) -AC_CHECK_LIB([ssl], - [SSL_CTX_new], - , - [AC_MSG_ERROR([libssl not found!])]) - -AC_CHECK_LIB([crypto], - [ASN1_STRING_to_UTF8], - , - [AC_MSG_ERROR([libcrypto not found!])]) - AC_CHECK_LIB([boost_system], [main], , @@ -118,16 +78,6 @@ AC_CHECK_LIB( [boost_iostreams], , [AC_MSG_ERROR([libboost_iostreams not found!])]) -AC_CHECK_LIB( [boost_log], - [main], - , - [AC_MSG_ERROR([libboost_log not found!])]) - -AC_CHECK_LIB( [boost_log_setup], - [main], - , - [AC_MSG_ERROR([libboost_log_setup not found!])]) - # TODO -> PARAMETRIZE THIS AC_CHECK_LIB([wthttp], [main], diff --git a/src/ui/docroot/css/lms.css b/docroot/css/lms.css similarity index 100% rename from src/ui/docroot/css/lms.css rename to docroot/css/lms.css diff --git a/src/ui/docroot/images/unknown-cover.jpg b/docroot/images/unknown-cover.jpg similarity index 100% rename from src/ui/docroot/images/unknown-cover.jpg rename to docroot/images/unknown-cover.jpg diff --git a/lms-api/proto/auth.proto b/lms-api/proto/auth.proto deleted file mode 100644 index 1ad02a48..00000000 --- a/lms-api/proto/auth.proto +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -package LmsAPI; - -message AuthRequest -{ - message Password - { - required string user_login = 1; - required string user_password = 2; - } - - enum Type - { - TypePassword = 1; - } - - required Type type = 1; - - optional Password password = 2; -} - -message AuthResponse -{ - - message PasswordResult - { - enum Type - { - TypePasswordInvalid = 1; - TypeLoginThrottling = 2; // The attempt was not processed because of throttling - TypePasswordValid = 3; - } - required Type type = 1; - - optional uint32 delay = 2; // in seconds, in case of PasswordInvalid/LoginThrottling - } - - enum Type - { - TypePasswordResult = 1; - } - - required Type type = 1; - - optional PasswordResult password_result = 2; -} - diff --git a/lms-api/proto/collection.proto b/lms-api/proto/collection.proto deleted file mode 100644 index 8666a98b..00000000 --- a/lms-api/proto/collection.proto +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -package LmsAPI; - -message AudioCollectionRequest -{ - - message SearchFilter - { - repeated uint64 artist_id = 1; // has one of these artists - repeated uint64 genre_id = 2; // has one of these genres - repeated uint64 release_id = 3; // hase one of these releases - repeated uint64 track_id = 4; // has one of theses tracks - } - - message BatchParameter - { - required uint32 offset = 1; // First element requested - required uint32 size = 2; // Number of elements requested. Server may not honor this size if too big - } - - message GetGenreList - { - required BatchParameter batch_parameter = 1; - optional SearchFilter search_filter = 2; - } - - message GetArtistList - { - required BatchParameter batch_parameter = 1; - optional SearchFilter search_filter = 2; - } - - message GetReleaseList - { - required BatchParameter batch_parameter = 1; - optional SearchFilter search_filter = 2; - } - - message GetTrackList - { - required BatchParameter batch_parameter = 1; - optional SearchFilter search_filter = 2; - } - - message GetCoverArt - { - enum Type - { - TypeGetCoverArtRelease = 1; - TypeGetCoverArtTrack = 2; - } - - required Type type = 1; - - optional uint64 release_id = 2; // Release that owns the cover art - optional uint64 track_id = 3; // Track that owns the cover art - - required uint32 size = 4; // Scale image to size*size pixels. Set 0 to get the biggest image - } - - enum Type - { - TypeGetRevision = 1; - TypeGetGenreList = 2; - TypeGetArtistList = 3; - TypeGetReleaseList = 4; - TypeGetTrackList = 5; - TypeGetCoverArt = 6; - } - - required Type type = 1; - - optional GetGenreList get_genres = 3; - optional GetArtistList get_artists = 4; - optional GetReleaseList get_releases = 5; - optional GetTrackList get_tracks = 6; - optional GetCoverArt get_cover_art = 7; -} - - -message AudioCollectionResponse -{ - - message Revision - { - required string rev = 1; // Unique identifier of the database revision - } - - message GenreList - { - repeated Genre genres = 1; - } - - message ArtistList - { - repeated Artist artists = 1; - } - - message ReleaseList - { - repeated Release releases = 1; - } - - message TrackList - { - repeated Track tracks = 1; - } - - message CoverArt - { - optional string mime_type = 1; - required bytes data = 2; - } - - message Genre - { - required uint64 id = 1; - optional string mbid = 2; - required string name = 3; - } - - message Artist - { - required uint64 id = 1; - optional string mbid = 2; - required string name = 3; - } - - message Release - { - required uint64 id = 1; - optional string mbid = 2; - required string name = 3; - } - - message Track - { - required uint64 id = 1; // Track id - optional string mbid = 2; - - required uint64 artist_id = 3; - required uint64 release_id = 4; - repeated uint64 genre_id = 5; - - optional string artist_mbid = 6; - optional string release_mbid = 7; - optional uint32 disc_number = 8; - optional uint32 track_number = 9; - required string name = 10; - required uint32 duration_secs = 11; - optional string release_date = 12; - optional string original_release_date = 13; - } - - enum Type { - TypeRevision = 1; - TypeGenreList = 2; - TypeArtistList = 3; - TypeReleaseList = 4; - TypeTrackList = 5; - TypeCoverArt = 6; - } - - - required Type type = 1; - - optional Revision revision = 2; - optional GenreList genre_list = 3; - optional ArtistList artist_list = 4; - optional ReleaseList release_list = 5; - optional TrackList track_list = 6; - repeated CoverArt cover_art = 7; -} - - - - diff --git a/lms-api/proto/media.proto b/lms-api/proto/media.proto deleted file mode 100644 index 20af5d02..00000000 --- a/lms-api/proto/media.proto +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -package LmsAPI; - -message MediaRequest -{ - - message Prepare - { - - enum AudioCodecType - { - AudioCodecTypeOGA = 1; - } - enum AudioBitrate { - AudioBitrate_32_kbps = 1; - AudioBitrate_64_kbps = 2; - AudioBitrate_96_kbps = 3; - AudioBitrate_128_kbps = 4; - AudioBitrate_192_kbps = 5; - AudioBitrate_256_kbps = 6; - } - - enum VideoCodecType - { - VideoCodecTypeOGV = 1; - } - enum VideoBitrate { - VideoBitrate_512_kbps = 1; - } - - message Audio - { - required int64 track_id = 1; // Id if the media - required AudioCodecType codec_type = 2; - required AudioBitrate bitrate = 3; - optional uint32 stream_idx = 4; - optional uint32 offset_secs = 5; - } - - message Video - { - required int64 video_id = 1; // Id if the media - required VideoCodecType codec_type = 2; - required AudioBitrate audio_bitrate = 3; - required VideoBitrate video_bitrate = 4; - optional uint32 offset_secs = 5; - optional uint32 audio_stream_idx = 6; - optional uint32 video_stream_idx = 7; - optional uint32 subtitle_stream_idx = 8; - } - - enum Type { - AudioRequest = 1; - VideoRequest = 2; - } - - required Type type = 1; - - optional Audio audio = 2; - optional Video video = 3; - } - - message GetPart - { - required uint32 handle = 1; // handle that uniquely identify the media. Get using the Prepare request - required uint32 requested_data_size = 2; // Amount of data requested. May receive more or less. May receive 0 byte if complete - } - - message Terminate - { - required uint32 handle = 1; - } - - enum Type - { - TypeMediaPrepare = 1; - TypeMediaGetPart = 2; - TypeMediaTerminate = 3; - } - - required Type type = 1; - - optional Prepare prepare = 2; - optional GetPart get_part = 3; - optional Terminate terminate = 4; - -} // MediaRequest - -message MediaResponse -{ - - message PrepareResult - { - optional uint32 handle = 1; // handle iif prepare request was ok - } - - message PartResult - { - optional bytes data = 2; // 0 bytes when the media is over, nothing when the request is ill formed - } - - message TerminateResult - { - } - - enum Type - { - TypePrepareResult = 1; - TypePartResult = 2; - TypeTerminateResult = 3; - } - - required Type type = 1; - - optional PrepareResult prepare_result = 2; - optional PartResult part_result = 3; - optional TerminateResult terminate_result = 4; -} - diff --git a/lms-api/proto/messages.proto b/lms-api/proto/messages.proto deleted file mode 100644 index a6eb280b..00000000 --- a/lms-api/proto/messages.proto +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -import "auth.proto"; -import "collection.proto"; -import "media.proto"; - -package LmsAPI; - -// Every request sent by the client must be a ClientMessage -// The server will answer using a ServerMessage -// -// The client must authenticate first before querying data -// If an expected message is received, the connection is dropped by the server -// -// A header is to be added before the serialized protobuf message in order to keep -// track of where one message ends and the next begins. -// Format: -// MAGIC (4 bytes, 0xDE, 0xAD, 0xBE, 0xEF)- SIZE (4 bytes, big endian encoded) - serialized proto message -message ClientMessage -{ - enum Type - { - AuthRequest = 1; - AudioCollectionRequest = 2; - MediaRequest = 3; - } - - required Type type = 1; - - optional AuthRequest auth_request = 2; - optional AudioCollectionRequest audio_collection_request = 3; - optional MediaRequest media_request = 4; -} - - -message ServerMessage -{ - enum Type - { - AuthResponse = 1; - AudioCollectionResponse = 2; - MediaResponse = 3; - } - - required Type type = 1; - - optional AuthResponse auth_response = 2; - optional AudioCollectionResponse audio_collection_response = 3; - optional MediaResponse media_response = 4; -} - - diff --git a/src/Makefile.am b/src/Makefile.am index e60d4e1c..ce6a0c12 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,14 +2,8 @@ bin_PROGRAMS = lms lms_SOURCES = \ $(srcdir)/main/main.cpp \ - $(srcdir)/av/CodecContext.cpp \ - $(srcdir)/av/Common.cpp \ - $(srcdir)/av/Dictionary.cpp \ - $(srcdir)/av/FormatContext.cpp \ - $(srcdir)/av/InputFormatContext.cpp \ - $(srcdir)/av/Stream.cpp \ - $(srcdir)/config/ConfigReader.cpp \ - $(srcdir)/cover/CoverArt.cpp \ + $(srcdir)/av/AvInfo.cpp \ + $(srcdir)/av/AvTranscoder.cpp \ $(srcdir)/cover/CoverArtGrabber.cpp \ $(srcdir)/database/Artist.cpp \ $(srcdir)/database/DatabaseHandler.cpp \ @@ -23,16 +17,12 @@ lms_SOURCES = \ $(srcdir)/database/Video.cpp \ $(srcdir)/database-updater/DatabaseUpdater.cpp \ $(srcdir)/database-updater/Checksum.cpp \ + $(srcdir)/image/Image.cpp \ $(srcdir)/logger/Logger.cpp \ $(srcdir)/metadata/AvFormat.cpp \ $(srcdir)/metadata/Utils.cpp \ $(srcdir)/service/ServiceManager.cpp \ $(srcdir)/service/DatabaseUpdateService.cpp \ - $(srcdir)/service/UserInterfaceService.cpp \ - $(srcdir)/transcode/AvConvTranscoder.cpp \ - $(srcdir)/transcode/Format.cpp \ - $(srcdir)/transcode/Parameters.cpp \ - $(srcdir)/transcode/InputMediaFile.cpp \ $(srcdir)/ui/LmsApplication.cpp \ $(srcdir)/ui/auth/LmsAuth.cpp \ $(srcdir)/ui/audio/desktop/DesktopAudio.cpp \ @@ -51,10 +41,6 @@ lms_SOURCES = \ $(srcdir)/ui/common/LineEdit.cpp \ $(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \ $(srcdir)/ui/resource/CoverResource.cpp \ - $(srcdir)/ui/video/VideoWidget.cpp \ - $(srcdir)/ui/video/VideoDatabaseWidget.cpp \ - $(srcdir)/ui/video/VideoMediaPlayerWidget.cpp \ - $(srcdir)/ui/video/VideoParametersDialog.cpp \ $(srcdir)/ui/settings/Settings.cpp \ $(srcdir)/ui/settings/SettingsAccountFormView.cpp \ $(srcdir)/ui/settings/SettingsAudioFormView.cpp \ @@ -65,51 +51,14 @@ lms_SOURCES = \ $(srcdir)/ui/settings/SettingsUserFormView.cpp \ $(srcdir)/ui/settings/SettingsUsers.cpp -if LMSAPI +if VIDEO lms_SOURCES += \ - $(srcdir)/lms-api/server/Connection.cpp \ - $(srcdir)/lms-api/server/ConnectionManager.cpp \ - $(srcdir)/lms-api/server/AudioCollectionRequestHandler.cpp \ - $(srcdir)/lms-api/server/AuthRequestHandler.cpp \ - $(srcdir)/lms-api/server/MediaRequestHandler.cpp \ - $(srcdir)/lms-api/server/RequestHandler.cpp \ - $(srcdir)/lms-api/server/Server.cpp \ - $(srcdir)/service/LmsAPIServerService.cpp - -nodist_lms_SOURCES = \ - $(builddir)/auth.pb.cc \ - $(builddir)/auth.pb.h \ - $(builddir)/collection.pb.cc \ - $(builddir)/collection.pb.h \ - $(builddir)/media.pb.cc \ - $(builddir)/media.pb.h \ - $(builddir)/messages.pb.cc \ - $(builddir)/messages.pb.h - -BUILT_SOURCES = \ - $(builddir)/auth.pb.cc \ - $(builddir)/auth.pb.h \ - $(builddir)/collection.pb.cc \ - $(builddir)/collection.pb.h \ - $(builddir)/media.pb.cc \ - $(builddir)/media.pb.h \ - $(builddir)/messages.pb.cc \ - $(builddir)/messages.pb.h - -MOSTLYCLEANFILES = \ - $(builddir)/auth.pb.cc \ - $(builddir)/auth.pb.h \ - $(builddir)/collection.pb.cc \ - $(builddir)/collection.pb.h \ - $(builddir)/media.pb.cc \ - $(builddir)/media.pb.h \ - $(builddir)/messages.pb.cc \ - $(builddir)/messages.pb.h - -%.pb.cc %.pb.h: $(top_srcdir)/lms-api/proto/%.proto - $(PROTOC) --proto_path=$(top_srcdir)/lms-api/proto/ --cpp_out=$(builddir)/ $^ - + $(srcdir)/ui/video/VideoWidget.cpp \ + $(srcdir)/ui/video/VideoDatabaseWidget.cpp \ + $(srcdir)/ui/video/VideoMediaPlayerWidget.cpp \ + $(srcdir)/ui/video/VideoParametersDialog.cpp endif -lms_CXXFLAGS=-DBOOST_LOG_DYN_LINK -std=c++11 -Wall -I$(top_srcdir)/third-party -I$(srcdir)/ui -I$(srcdir)/lms-api +lms_CXXFLAGS=-std=c++11 -Wall -I$(top_srcdir)/third-party -I$(srcdir)/ui $(MAGICKXX_CFLAGS) +lms_LDADD=$(MAGICKXX_LIBS) diff --git a/src/av/AvInfo.cpp b/src/av/AvInfo.cpp new file mode 100644 index 00000000..68390e30 --- /dev/null +++ b/src/av/AvInfo.cpp @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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 3 of the License, or + * (at your option) any later version. + * + * LMS 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 LMS. If not, see . + */ + +#include + +#include "logger/Logger.hpp" + +#include "AvInfo.hpp" + +namespace Av { + +static std::string streamType_to_string(Stream::Type type) +{ + switch (type) + { + case Stream::Type::Audio: return "audio"; + case Stream::Type::Video: return "video"; + case Stream::Type::Subtitle: return "subtitle"; + } + + return "unknown"; +} + +static std::string averror_to_string(int error) +{ + std::array buf = {0}; + + if (av_strerror(error, buf.data(), buf.size()) == 0) + return std::string(&buf[0]); + else + return "Unknown error"; +} + + +void AvInit() +{ + /* register all the codecs */ + avcodec_register_all(); + av_register_all(); + LMS_LOG(AV, INFO) << "avcodec version = " << avcodec_version(); +} + + + + +MediaFile::MediaFile(const boost::filesystem::path& p) +: _p(p), _context(nullptr) +{ +} + +MediaFile::~MediaFile() +{ + if (_context != nullptr) + avformat_close_input(&_context); +} + +bool +MediaFile::open(void) +{ + if (_context != nullptr) + throw std::logic_error("inputfile '" + _p.string() + "' already open"); + + int error = avformat_open_input(&_context, _p.string().c_str(), nullptr, nullptr); + if (error < 0) + { + LMS_LOG(AV, ERROR) << "Cannot open '" << _p.string() << "', avformat_open_input returned " << averror_to_string(error); + return false; + } + + return true; +} + +bool +MediaFile::scan(void) +{ + if (_context == nullptr) + throw std::logic_error("inputfile '" + _p.string() + "' not open"); + + int error = avformat_find_stream_info(_context, nullptr); + if (error < 0) + { + LMS_LOG(AV, ERROR) << "Cannot find stream information: '" << averror_to_string(error); + return false; + } + + return true; +} + +boost::posix_time::time_duration +MediaFile::getDuration() const +{ + if (_context == nullptr) + throw std::logic_error("inputfile '" + _p.string() + "' not open"); + + if (static_cast(_context->duration) != AV_NOPTS_VALUE ) + return boost::posix_time::seconds(_context->duration / AV_TIME_BASE); + else + return boost::posix_time::seconds(0); // TODO, do something better? +} + +void +getMetaDataFromDictionnary(AVDictionary* dictionnary, std::map& res) +{ + if (!dictionnary) + return; + + AVDictionaryEntry *tag = NULL; + while ((tag = av_dict_get(dictionnary, "", tag, AV_DICT_IGNORE_SUFFIX))) + { + res.insert( std::make_pair(tag->key, tag->value)); + } +} + +std::map +MediaFile::getMetaData(void) +{ + if (_context == nullptr) + throw std::logic_error("inputfile '" + _p.string() + "' not open"); + + std::map res; + + getMetaDataFromDictionnary(_context->metadata, res); + + // HACK for OGG files + // If we did not find tags, search metadata in streams + if (res.empty()) + { + for (std::size_t i = 0; i < _context->nb_streams; ++i) + { + getMetaDataFromDictionnary(_context->streams[i]->metadata, res); + + if (!res.empty()) + break; + } + } + + return res; +} + +std::vector +MediaFile::getStreams(Stream::Type type) const +{ + if (_context == nullptr) + throw std::logic_error("inputfile '" + _p.string() + "' not open"); + + std::vector res; + + for (std::size_t i = 0; i < _context->nb_streams; ++i) + { + AVStream* avstream = _context->streams[i]; + + // Skip attached pics + if (avstream->disposition & AV_DISPOSITION_ATTACHED_PIC) + continue; + + if (avstream->codec == nullptr) + { + LMS_LOG(AV, ERROR) << "Skipping stream " << i << " since no codec is set"; + continue; + } + + if (type == Stream::Type::Audio && avstream->codec->codec_type != AVMEDIA_TYPE_AUDIO) + continue; + else if (type == Stream::Type::Video && avstream->codec->codec_type != AVMEDIA_TYPE_VIDEO) + continue; + else if (type == Stream::Type::Subtitle && avstream->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) + continue; + + Stream stream; + + stream.id = i; // or use stream->id ? + stream.type = type; + stream.bitrate = avstream->codec->bit_rate; + + { + std::array buf = {0}; + avcodec_string(buf.data(), buf.size(), avstream->codec, 0); + + stream.desc = buf.data(); + } + + res.push_back(stream); + } + + return res; +} + +int +MediaFile::getBestStreamId(Stream::Type type) const +{ + if (_context == nullptr) + throw std::logic_error("inputfile '" + _p.string() + "' not open"); + + enum AVMediaType avMediaType; + + switch (type) + { + case Stream::Type::Audio: avMediaType = AVMEDIA_TYPE_AUDIO; break; + case Stream::Type::Video: avMediaType = AVMEDIA_TYPE_VIDEO; break; + case Stream::Type::Subtitle: avMediaType = AVMEDIA_TYPE_SUBTITLE; break; + default: + return -1; + } + + int res = av_find_best_stream(_context, + avMediaType, + -1, // Auto + -1, // Auto + NULL, + 0); + if (res < 0) + { + LMS_LOG(AV, ERROR) << "Cannot find best stream for type " << streamType_to_string(type); + return -1; + } + + return res; +} + +bool +MediaFile::hasAttachedPictures(void) const +{ + for (std::size_t i = 0; i < _context->nb_streams; ++i) + { + if (_context->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC) + return true; + } + + return false; +} + +std::vector +MediaFile::getAttachedPictures(std::size_t nbMaxPictures) const +{ + if (_context == nullptr) + throw std::logic_error("inputfile '" + _p.string() + "' not open"); + + static const std::map codecMimeMap = + { + { AV_CODEC_ID_BMP, "image/x-bmp" }, + { AV_CODEC_ID_GIF, "image/gif" }, + { AV_CODEC_ID_MJPEG, "image/jpeg" }, + { AV_CODEC_ID_PNG, "image/png" }, + { AV_CODEC_ID_PNG, "image/x-png" }, + { AV_CODEC_ID_PPM, "image/x-portable-pixmap" }, + }; + + std::vector pictures; + + for (std::size_t i = 0; i < _context->nb_streams; ++i) + { + AVStream *avstream = _context->streams[i]; + + // Skip attached pics + if (!(avstream->disposition & AV_DISPOSITION_ATTACHED_PIC)) + continue; + + if (avstream->codec == nullptr) + { + LMS_LOG(AV, ERROR) << "Skipping stream " << i << " since no codec is set"; + continue; + } + + Picture picture; + + auto itMime = codecMimeMap.find(avstream->codec->codec_id); + if (itMime != codecMimeMap.end()) + { + picture.mimeType = itMime->second; + } + else + { + picture.mimeType = "application/octet-stream"; + LMS_LOG(AV, ERROR) << "CODEC ID " << avstream->codec->codec_id << " not handled in mime type conversion"; + } + + AVPacket pkt = avstream->attached_pic; + + std::copy(pkt.data, pkt.data + pkt.size, std::back_inserter(picture.data)); + + pictures.push_back( picture ); + + if (pictures.size() >= nbMaxPictures) + break; + } + + return pictures; +} + +} // namespace Av diff --git a/src/av/AvInfo.hpp b/src/av/AvInfo.hpp new file mode 100644 index 00000000..377fd6fd --- /dev/null +++ b/src/av/AvInfo.hpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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 3 of the License, or + * (at your option) any later version. + * + * LMS 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 LMS. If not, see . + */ + +/* This file contains some classes in order to get info from file using the libavconv */ + +#ifndef AV_INFO_HPP +#define AV_INFO_HPP + +extern "C" +{ +#define __STDC_CONSTANT_MACROS +#include +#include +#include +} + +#include +#include +#include +#include + +#include +#include //no i/o just types + +namespace Av +{ + +void AvInit(); + +struct Picture +{ + std::string mimeType; + std::vector data; +}; + +struct Stream +{ + enum class Type + { + Audio, + Video, + Subtitle, + }; + + int id; + Type type; + std::size_t bitrate; + std::string desc; // Description of the stream +}; + +class MediaFile +{ + public: + + MediaFile(const boost::filesystem::path& p); + ~MediaFile(); + + // non copyable + MediaFile(const MediaFile&) = delete; + MediaFile& operator=(const MediaFile&) = delete; + + boost::filesystem::path getPath() const {return _p;}; + + bool open(void); + bool scan(void); + + boost::posix_time::time_duration getDuration() const; + std::map getMetaData(void); + + std::vector getStreams(Stream::Type type) const; + int getBestStreamId(Stream::Type type) const; // -1 if failure/unknown + bool hasAttachedPictures(void) const; + std::vector getAttachedPictures(std::size_t nbMaxPictures) const; + + private: + MediaFile(); + + boost::filesystem::path _p; + AVFormatContext* _context; +}; + + +} // namespace Av + +#endif + diff --git a/src/transcode/AvConvTranscoder.cpp b/src/av/AvTranscoder.cpp similarity index 54% rename from src/transcode/AvConvTranscoder.cpp rename to src/av/AvTranscoder.cpp index 70b056fa..a4130a20 100644 --- a/src/transcode/AvConvTranscoder.cpp +++ b/src/av/AvTranscoder.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2015 Emeric Poupon * * This file is part of LMS. * @@ -17,18 +17,29 @@ * along with LMS. If not, see . */ -#include - -#include -#include -#include - #include "logger/Logger.hpp" -#include "AvConvTranscoder.hpp" +#include "AvTranscoder.hpp" -namespace Transcode +namespace Av { + +std::string encoding_to_mimetype(Encoding encoding) { + switch(encoding) + { + case Encoding::MP3: return "audio/mp3"; + case Encoding::OGA: return "audio/ogg"; + case Encoding::OGV: return "video/ogg"; + case Encoding::WEBMA: return "audio/webm"; + case Encoding::WEBMV: return "video/webm"; + case Encoding::FLA: return "audio/x-flv"; + case Encoding::FLV: return "video/x-flv"; + case Encoding::M4A: return "audio/mp4"; + case Encoding::M4V: return "video/mp4"; + } + + return ""; +} // TODO, parametrize? const std::vector execNames = @@ -37,15 +48,14 @@ const std::vector execNames = "ffmpeg", }; +boost::mutex Transcoder::_mutex; -boost::mutex AvConvTranscoder::_mutex; - -boost::filesystem::path AvConvTranscoder::_avConvPath = boost::filesystem::path(); +boost::filesystem::path Transcoder::_avConvPath = boost::filesystem::path(); void -AvConvTranscoder::init() +Transcoder::init() { - BOOST_FOREACH(std::string execName, execNames) + for (std::string execName : execNames) { const boost::filesystem::path p = boost::process::search_path(execName); if (!p.empty()) @@ -56,32 +66,35 @@ AvConvTranscoder::init() } if (!_avConvPath.empty()) - LMS_LOG(MOD_TRANSCODE, SEV_INFO) << "Using transcoder " << _avConvPath; + LMS_LOG(TRANSCODE, INFO) << "Using transcoder " << _avConvPath; else throw std::runtime_error("Cannot find any transcoder binary!"); } -//boost::filesystem::path AvConvTranscoder::_avConvPath = ""; +//boost::filesystem::path Transcoder::_avConvPath = ""; -AvConvTranscoder::AvConvTranscoder(const Parameters& parameters) -: _parameters(parameters), +Transcoder::Transcoder(boost::filesystem::path filePath, TranscodeParameters parameters) +: _filePath(filePath), + _parameters(parameters), _outputPipe(boost::process::create_pipe()), _source(_outputPipe.source, boost::iostreams::close_handle), _is(_source), _in(&_is), - _isComplete(false), - _outputBytes(0) + _isComplete(false) { - if (!boost::filesystem::exists(_parameters.getInputMediaFile().getPath())) { - throw std::runtime_error("File " + _parameters.getInputMediaFile().getPath().string() + " does not exists!"); - } - else if (!boost::filesystem::is_regular( _parameters.getInputMediaFile().getPath())) { - throw std::runtime_error("File " + _parameters.getInputMediaFile().getPath().string() + " is not regular!"); - } +} - LMS_LOG(MOD_TRANSCODE, SEV_INFO) << "Transcoding file '" << _parameters.getInputMediaFile().getPath() << "'"; +bool +Transcoder::start() +{ + if (!boost::filesystem::exists(_filePath)) + return false; + else if (!boost::filesystem::is_regular( _filePath) ) + return false; + + LMS_LOG(TRANSCODE, INFO) << "Transcoding file '" << _filePath << "'"; // Launch a process to handle the conversion boost::iostreams::file_descriptor_sink sink(_outputPipe.sink, boost::iostreams::close_handle); @@ -92,66 +105,59 @@ AvConvTranscoder::AvConvTranscoder(const Parameters& parameters) // input Offset if (_parameters.getOffset().total_seconds() > 0) - oss << " -ss " << _parameters.getOffset().total_seconds(); // to be placed before '-i' to speed up seeking + oss << " -ss " << _parameters.getOffset().total_seconds(); // to be placed before '-i' to speed up seeking? // Input file - oss << " -i \"" << _parameters.getInputMediaFile().getPath().string() << "\""; + oss << " -i " << _filePath; // Output bitrates - oss << " -b:a " << _parameters.getOutputBitrate(Stream::Audio) ; - if (_parameters.getOutputFormat().getType() == Format::Video) - oss << " -b:v " << _parameters.getOutputBitrate(Stream::Video); + oss << " -b:a " << _parameters.getBitrate(Stream::Type::Audio) ; +// if (_parameters.getOutputFormat().getType() == Format::Video) +// oss << " -b:v " << _parameters.getOutputBitrate(Stream::Video); // Stream mapping + for (int streamId : _parameters.getSelectedStreamIds()) { - typedef std::map StreamMap; - - const StreamMap& streamMap = _parameters.getInputStreams(); - - BOOST_FOREACH(const StreamMap::value_type& inputStream, streamMap) - { - // HACK (no subtitle support yet) - if (inputStream.first != Stream::Subtitle) - oss << " -map 0:" << inputStream.second; // 0 means input file index - } + // 0 means the first input file + oss << " -map 0:" << streamId; } // Codecs and formats - switch( _parameters.getOutputFormat().getEncoding()) + switch( _parameters.getEncoding()) { - case Format::MP3: + case Encoding::MP3: oss << " -f mp3"; break; - case Format::OGA: + case Encoding::OGA: oss << " -acodec libvorbis -f ogg"; break; - case Format::OGV: + case Encoding::OGV: oss << " -acodec libvorbis -ac 2 -ar 44100 -vcodec libtheora -threads 4 -f ogg"; break; - case Format::WEBMA: + case Encoding::WEBMA: oss << " -codec:a libvorbis -f webm"; break; - case Format::WEBMV: + case Encoding::WEBMV: oss << " -acodec libvorbis -ac 2 -ar 44100 -vcodec libvpx -threads 4 -f webm"; break; - case Format::M4A: + case Encoding::M4A: oss << " -acodec aac -f mp4 -strict experimental"; break; - case Format::M4V: + case Encoding::M4V: oss << " -acodec aac -strict experimental -ac 2 -ar 44100 -vcodec libx264 -f m4v"; break; - case Format::FLV: + case Encoding::FLV: oss << " -acodec libmp3lame -ac 2 -ar 44100 -vcodec libx264 -f flv"; break; - case Format::FLA: + case Encoding::FLA: oss << " -acodec libmp3lame -f flv"; break; default: - assert(0); + return false; } oss << " -"; // output to stdout - LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Executing '" << oss.str() << "'"; + LMS_LOG(TRANSCODE, DEBUG) << "Executing '" << oss.str() << "'"; // make sure only one thread is executing this part of code // See boost process FAQ @@ -167,10 +173,11 @@ AvConvTranscoder::AvConvTranscoder(const Parameters& parameters) ); } + return true; } void -AvConvTranscoder::process(std::vector& output, std::size_t maxSize) +Transcoder::process(std::vector& output, std::size_t maxSize) { std::size_t readDataSize = 0; @@ -181,11 +188,10 @@ AvConvTranscoder::process(std::vector& output, std::size_t maxSiz while(readDataSize < maxSize && _in && _in.get(ch)) { output.push_back(ch); readDataSize++; - _outputBytes++; // stats } if (!_in || _in.fail() || _in.eof()) { - LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Transcode complete!"; + LMS_LOG(TRANSCODE, DEBUG) << "Transcode complete!"; waitChild(); _isComplete = true; @@ -193,9 +199,9 @@ AvConvTranscoder::process(std::vector& output, std::size_t maxSiz } -AvConvTranscoder::~AvConvTranscoder() +Transcoder::~Transcoder() { - LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "~AvConvTranscoder called!"; + LMS_LOG(TRANSCODE, DEBUG) << "~Transcoder called!"; if (_in.eof()) waitChild(); @@ -204,40 +210,46 @@ AvConvTranscoder::~AvConvTranscoder() } void -AvConvTranscoder::waitChild() +Transcoder::waitChild() { if (_child) { boost::system::error_code ec; - LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Waiting for child..."; + LMS_LOG(TRANSCODE, DEBUG) << "Waiting for child..."; boost::process::wait_for_exit(*_child, ec); - LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Waiting for child: OK"; + LMS_LOG(TRANSCODE, DEBUG) << "Waiting for child: OK"; if (ec) - LMS_LOG(MOD_TRANSCODE, SEV_ERROR) << "AvConvTranscoder::waitChild: error: " << ec.message(); + LMS_LOG(TRANSCODE, ERROR) << "Transcoder::waitChild: error: " << ec.message(); _child.reset(); } } void -AvConvTranscoder::killChild() +Transcoder::killChild() { if (_child) { boost::system::error_code ec; - LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Killing child! pid = " << _child->pid; + LMS_LOG(TRANSCODE, DEBUG) << "Killing child! pid = " << _child->pid; boost::process::terminate(*_child, ec); - LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Killing child DONE"; + LMS_LOG(TRANSCODE, DEBUG) << "Killing child DONE"; // If an error occured, force kill the child if (ec) - LMS_LOG(MOD_TRANSCODE, SEV_ERROR) << "AvConvTranscoder::killChild: error: " << ec.message(); + LMS_LOG(TRANSCODE, ERROR) << "Transcoder::killChild: error: " << ec.message(); _child.reset(); } } +bool +Transcoder::isComplete(void) +{ + return _isComplete; +} + } // namespace Transcode diff --git a/src/av/AvTranscoder.hpp b/src/av/AvTranscoder.hpp new file mode 100644 index 00000000..40a00b7a --- /dev/null +++ b/src/av/AvTranscoder.hpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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 3 of the License, or + * (at your option) any later version. + * + * LMS 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 LMS. If not, see . + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include //no i/o just types +#include + +#include "AvInfo.hpp" + +namespace Av { + + +enum class Encoding +{ + OGA, + OGV, + MP3, + WEBMA, + WEBMV, + FLA, + FLV, + M4A, + M4V, +}; + +std::string encoding_to_mimetype(Encoding encoding); + +class TranscodeParameters +{ + public: + + // Setters + void setEncoding(Encoding encoding) { _encoding = encoding; } + void setOffset(boost::posix_time::time_duration offset) {_offset = offset; } + void setBitrate(Stream::Type type, std::size_t bitrate) { _outputBitrate[type] = bitrate; } + + // Manually add the streams to be transcoded + // If no stream is added, input streams are selected automatically + void addStream(int inputStreamId) { _selectedStreams.insert(inputStreamId); } + + // Getters + Encoding getEncoding(void) const { return _encoding; } + boost::posix_time::time_duration getOffset(void) const { return _offset; } + std::set getSelectedStreamIds(void) const { return _selectedStreams; } + std::size_t getBitrate(Stream::Type type) { return _outputBitrate[type]; } + + private: + Encoding _encoding = Encoding::MP3; + boost::posix_time::time_duration _offset = boost::posix_time::seconds(0); + std::set _selectedStreams; + std::map _outputBitrate = { {Stream::Type::Audio, 0}, { Stream::Type::Video, 0}, { Stream::Type::Subtitle, 0} }; +}; + + +class Transcoder +{ + public: + static void init(); + + Transcoder(boost::filesystem::path file, TranscodeParameters parameters); + ~Transcoder(); + + // non copyable + Transcoder(const Transcoder&) = delete; + Transcoder& operator=(const Transcoder&) = delete; + + bool start(); + void process(std::vector& output, std::size_t maxSize); + bool isComplete(void); + + private: + + Transcoder(); + + boost::filesystem::path _filePath; + TranscodeParameters _parameters; + + static boost::mutex _mutex; + + boost::process::pipe _outputPipe; + boost::iostreams::file_descriptor_source _source; + boost::iostreams::stream_buffer _is; + std::istream _in; + + void waitChild(); + void killChild(); + + std::shared_ptr _child; + + static boost::filesystem::path _avConvPath; + bool _isComplete; + +}; + +} // namespace Av + diff --git a/src/av/Codec.cpp b/src/av/Codec.cpp deleted file mode 100644 index d5e6e7e3..00000000 --- a/src/av/Codec.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#include -#include -#include - -#include "logger/Logger.hpp" - -#include "Codec.hpp" - -Codec::Codec(enum CodecID codec, Type type ) -: _codec(nullptr) -{ - if (type == Encoder) - _codec = avcodec_find_encoder(codec); - else if (type == Decoder) - _codec = avcodec_find_decoder(codec); - - if (_codec == nullptr) { - LMS_LOG(MOD_AV, SEV_ERROR) << "Codec constructor failed! codec = " << codec << ", type = " << type; - throw std::runtime_error("can't find codec using this id!"); - } -} - -Codec::Codec(const AVCodec* codec) -: _codec(codec) -{ - assert(_codec != nullptr); -} - -Codec::~Codec() -{ - if (_codec == nullptr) { - // TODO release iif ownership has not been taken? -// av_codec_close(_codec); - } -} - -const AVCodec* -Codec::get() const -{ - assert(_codec != nullptr); - return _codec; -} - -Codec::Id -Codec::getId() const -{ - assert(_codec != nullptr); - return _codec->id; -} - -std::string -Codec::getName() const -{ - assert(_codec != nullptr); - return _codec->name; -} - diff --git a/src/av/Codec.hpp b/src/av/Codec.hpp deleted file mode 100644 index 94220355..00000000 --- a/src/av/Codec.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#ifndef CODEC_HPP__ -#define CODEC_HPP__ - -#include -#include -#include - -#include "Common.hpp" - -class Codec : boost::noncopyable -{ - friend class CodecContext; - friend class FormatContext; - friend class OutputFormatContext; - - public: - - typedef enum AVCodecID Id; - enum Type { - Encoder, - Decoder, - }; - - Codec(Id codecId, Type type); - Codec(const AVCodec* codec); // Attach existing codec - ~Codec(); - - Id getId() const; - std::string getName() const; - - private: - - const AVCodec* get() const; - const AVCodec* _codec; -}; - -#endif diff --git a/src/av/CodecContext.cpp b/src/av/CodecContext.cpp deleted file mode 100644 index 1f9ee44c..00000000 --- a/src/av/CodecContext.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#include -#include -#include -#include - -#include "CodecContext.hpp" - -namespace Av -{ - -CodecContext::CodecContext(AVCodecContext* CodecContext) -: _codecContext(CodecContext) -{ - assert(_codecContext != nullptr); -} - - -void -CodecContext::dumpInfo(std::ostream& ost) const -{ - ost << "BitRate = " << getBitRate() << ", SampleFormat = " << getSampleFormat() << ", SampleRate = " << getSampleRate() << ", ChannelLayout = " << getChannelLayout() << ", NbChannels = " << getNbChannels() << ", Timebase = " << getTimeBase().num << "/" << getTimeBase().den; -} - - -std::string -CodecContext::getCodecDesc(void) const -{ - std::array buf; - avcodec_string(buf.data(), buf.size(), _codecContext, 0); - - return std::string(buf.data()); -} - -} // namespace Av - diff --git a/src/av/CodecContext.hpp b/src/av/CodecContext.hpp deleted file mode 100644 index 4796a540..00000000 --- a/src/av/CodecContext.hpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#ifndef CODEC_CONTEXT_HPP -#define CODEC_CONTEXT_HPP - -#include -#include - -#include "Codec.hpp" - -namespace Av -{ - -class CodecContext -{ - public: - - CodecContext(AVCodecContext* CodecContext); // Attach existing codec context (no free will be done) - -// Codec getCodec(); - - enum AVMediaType getType(void) const { return _codecContext->codec_type; } - Codec::Id getCodecId(void) const { return _codecContext->codec_id; } - - std::string getCodecDesc(void) const; - - // Accessors - std::size_t getBitRate() const {return _codecContext->bit_rate;} - AVSampleFormat getSampleFormat() const {return _codecContext->sample_fmt;} - std::size_t getSampleRate() const {return _codecContext->sample_rate;} - std::uint64_t getChannelLayout() const {return _codecContext->channel_layout;} - std::size_t getNbChannels() const {return _codecContext->channels; } - AVRational getTimeBase() const {return _codecContext->time_base; } - - void dumpInfo(std::ostream& ost) const; - - private: - AVCodecContext* native() { return _codecContext; } - - AVCodecContext* _codecContext; -}; - -} // namespace Av - -#endif - diff --git a/src/av/Common.cpp b/src/av/Common.cpp deleted file mode 100644 index 24778fa9..00000000 --- a/src/av/Common.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#include - -#include "logger/Logger.hpp" - -#include "Common.hpp" - -namespace Av -{ - -std::string -AvError::to_str(void) const -{ - boost::array buf = {0}; - if (av_strerror(_errnum, buf.data(), buf.size()) == 0) - return std::string(&buf[0]); - else - return "Unknown error"; - -} - - -std::ostream& operator<<(std::ostream& ost, const AvError& err) -{ - ost << err.to_str(); - return ost; -} - -void AvInit() -{ - /* register all the codecs */ - avcodec_register_all(); - av_register_all(); - LMS_LOG(MOD_AV, SEV_INFO) << "avcodec version = " << avcodec_version(); -} - -} // namespace Av diff --git a/src/av/Common.hpp b/src/av/Common.hpp deleted file mode 100644 index ddb1b17b..00000000 --- a/src/av/Common.hpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#ifndef TRANSCODE_COMMON_HPP__ -#define TRANSCODE_COMMON_HPP__ - -// Hack to properly iinclude libavcodec/avcodec.h... -// -#define __STDC_CONSTANT_MACROS -#ifdef _STDINT_H -#undef _STDINT_H -#endif -# include -extern "C" { -#include -#include -#include -#include -} - -#include - -namespace Av -{ - - -void AvInit(); - -class AvError -{ - public: - AvError() : _errnum(0) {} - AvError(int ernum) : _errnum(ernum) {} - - void operator=(int errnum) { _errnum = errnum; } - - operator bool() { return _errnum < 0; } - - std::string to_str() const; - - friend std::ostream& operator<<(std::ostream& ost, const AvError&); - - bool eof() { return _errnum == AVERROR_EOF; } // TODO - - private: - int _errnum; -}; - -} // namespace Av - -#endif diff --git a/src/av/Dictionary.cpp b/src/av/Dictionary.cpp deleted file mode 100644 index 0466e351..00000000 --- a/src/av/Dictionary.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#include "Dictionary.hpp" - - -namespace Av -{ - -Dictionary::Dictionary(AVDictionary* dictionary) -: _dictionary(dictionary) -{ -} - -void -Dictionary::get(std::map& entries) -{ - AVDictionaryEntry *tag = NULL; - while ((tag = av_dict_get(_dictionary, "", tag, AV_DICT_IGNORE_SUFFIX))) { - entries.insert( std::make_pair(tag->key, tag->value)); - } -} - -std::string -Dictionary::get(std::string key) -{ - AVDictionaryEntry *tag = NULL; - tag = av_dict_get(_dictionary, key.c_str(), tag, 0); - - return tag != NULL ? std::string(tag->value) : std::string(); -} - -} // namespace Av - diff --git a/src/av/Dictionary.hpp b/src/av/Dictionary.hpp deleted file mode 100644 index cbb9f70c..00000000 --- a/src/av/Dictionary.hpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#ifndef AV_DICTIONARY_HPP -#define AV_DICTIONARY_HPP - -#include -#include "Common.hpp" - -namespace Av -{ - -class Dictionary -{ - public: - Dictionary(AVDictionary* dictionary); - - // Get all - void get(std::map& entries); - - // get a single entry - std::string get(std::string key); - - private: - AVDictionary* _dictionary; -}; - -} // namespace Av - -#endif - diff --git a/src/av/FormatContext.cpp b/src/av/FormatContext.cpp deleted file mode 100644 index 56a52c7f..00000000 --- a/src/av/FormatContext.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - - -#include "FormatContext.hpp" - -namespace Av -{ - -FormatContext::FormatContext() -: _context(nullptr) -{ -} - -FormatContext::~FormatContext() -{ -} - -} // namespace Av diff --git a/src/av/InputFormatContext.cpp b/src/av/InputFormatContext.cpp deleted file mode 100644 index 448a6aed..00000000 --- a/src/av/InputFormatContext.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#include -#include - -#include "logger/Logger.hpp" - -#include "InputFormatContext.hpp" - -namespace Av -{ - -InputFormatContext::InputFormatContext(const boost::filesystem::path& p) -: _path(p) -{ - AVFormatContext* context = nullptr; - - // The last three parameters specify the file format, buffer size and - // format parameters. By simply specifying NULL or 0 we ask libavformat - // to auto-detect the format and use a default buffer size. - AvError error = avformat_open_input(&context, p.string().c_str(), nullptr, nullptr); - if (error) - { - LMS_LOG(MOD_AV, SEV_ERROR) << "Cannot open '" << p.string() << "', avformat_open_input returned " << error; - throw std::runtime_error("avformat_open_input failed: " + error.to_str()); - } - - native(context); - -} - -InputFormatContext::~InputFormatContext() -{ - AVFormatContext* context = native(); - avformat_close_input(&context); -} - -std::vector -InputFormatContext::getStreams(void) -{ - std::vector res; - - for (std::size_t i = 0; i < native()->nb_streams; ++i) { - res.push_back( Stream(native()->streams[i])); - } - - return res; -} - - -bool -InputFormatContext::getBestStreamIdx(AVMediaType type, Stream::Idx& index) -{ - int res = av_find_best_stream(native(), - type, - -1, // Auto - -1, // Auto - NULL, - 0 - ); - - AvError error(res); - if (error) { - LMS_LOG(MOD_AV, SEV_DEBUG) << "Cannot get best stream for type " << type << ": " << error; - return false; - } - else { - index = res; - return true; - } -} - -bool -InputFormatContext::findStreamInfo(void) -{ - AvError err = avformat_find_stream_info(native(), NULL); - if (err) - { - LMS_LOG(MOD_AV, SEV_ERROR) << "Couldn't find stream information: " << err; - return false; - } - - return true; -} - - -std::size_t -InputFormatContext::getDurationSecs() const -{ - if (static_cast(native()->duration) != AV_NOPTS_VALUE ) - return native()->duration / AV_TIME_BASE; - else - return 0; // TODO, do something better? -} - -Dictionary -InputFormatContext::getMetadata(void) -{ - return Dictionary(native()->metadata); - -} - -std::size_t -InputFormatContext::getNbPictures(void) const -{ - std::size_t res = 0; - - for (std::size_t i = 0; i < native()->nb_streams; ++i) - { - if (native()->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC) - ++res; - } - return res; -} - -std::vector -InputFormatContext::getPictures(std::size_t nbMaxPictures) const -{ - static const std::map codecMimeMap = - { - { AV_CODEC_ID_BMP, "image/x-bmp" }, - { AV_CODEC_ID_GIF, "image/gif" }, - { AV_CODEC_ID_MJPEG, "image/jpeg" }, - { AV_CODEC_ID_PNG, "image/png" }, - { AV_CODEC_ID_PNG, "image/x-png" }, - { AV_CODEC_ID_PPM, "image/x-portable-pixmap" }, - }; - - std::vector pictures; - - for (std::size_t i = 0; i < native()->nb_streams; ++i) - { - Stream stream(native()->streams[i]); - - if (stream.hasAttachedPic()) - { - Picture picture; - - auto itMime = codecMimeMap.find(stream.getCodecContext().getCodecId()); - if (itMime != codecMimeMap.end()) - picture.mimeType = itMime->second; - else - picture.mimeType = "application/octet-stream"; - - AVPacket pkt = native()->streams[i]->attached_pic; - - std::copy(pkt.data, pkt.data + pkt.size, std::back_inserter(picture.data)); - - pictures.push_back( picture ); - - if (pictures.size() >= nbMaxPictures) - break; - } - } - - return pictures; -} - -} //namespace Av diff --git a/src/av/InputFormatContext.hpp b/src/av/InputFormatContext.hpp deleted file mode 100644 index 17620287..00000000 --- a/src/av/InputFormatContext.hpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#ifndef INPUT_FORMAT_CONTEXT_HPP -#define INPUT_FORMAT_CONTEXT_HPP - -#include - -#include - -#include "FormatContext.hpp" -#include "Stream.hpp" -#include "Dictionary.hpp" - -namespace Av -{ - -struct Picture { - std::string mimeType; - std::vector data; -}; - -class InputFormatContext : public FormatContext -{ - public: - - InputFormatContext(const boost::filesystem::path& p); - ~InputFormatContext(); - - - Dictionary getMetadata(void); // metadata access - - // Scan file - bool findStreamInfo(); - - - // Get attached pictures - std::size_t getNbPictures(void) const; - std::vector getPictures(std::size_t nbMaxPictures) const; - - // Get the streams - std::vector getStreams(void); - - bool getBestStreamIdx(enum AVMediaType type, Stream::Idx& idx); - std::size_t getDurationSecs() const; - - private: - boost::filesystem::path _path; - - -}; - -} // namespace av - -#endif - diff --git a/src/av/Stream.cpp b/src/av/Stream.cpp deleted file mode 100644 index 4a6875f0..00000000 --- a/src/av/Stream.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#include -#include "Stream.hpp" - -namespace Av -{ - -Stream::Stream(AVStream* stream) -: _stream(stream) -{ - assert(_stream != nullptr); - assert(_stream->codec != nullptr); -} - -CodecContext -Stream::getCodecContext() -{ - return _stream->codec; -} - -Dictionary -Stream::getMetadata(void) -{ - return Dictionary(_stream->metadata); -} - -bool -Stream::hasAttachedPic(void) const -{ - return (_stream->disposition & AV_DISPOSITION_ATTACHED_PIC); -} - -} // namespace Av diff --git a/src/av/Stream.hpp b/src/av/Stream.hpp deleted file mode 100644 index 20caab96..00000000 --- a/src/av/Stream.hpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#ifndef STREAM_HPP__ -#define STREAM_HPP__ - -#include "Common.hpp" - -#include "CodecContext.hpp" -#include "Dictionary.hpp" - -namespace Av -{ - -class Stream -{ - friend class InputFormatContext; - - public: - // Attach existing stream - Stream(AVStream* stream); - - typedef size_t Idx; - -// Idx getIdx() const { return _stream->index; } - - bool hasAttachedPic(void) const; - Dictionary getMetadata(void); - CodecContext getCodecContext(void); - - private: - - AVStream* _stream; -}; - -} // namespace Av - -#endif - diff --git a/src/config/ConfigReader.cpp b/src/config/ConfigReader.cpp deleted file mode 100644 index 1598e71b..00000000 --- a/src/config/ConfigReader.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#include - -#include "ConfigReader.hpp" - -namespace { - -} - -ConfigReader::ConfigReader() -: _config (nullptr) -{ -} - -ConfigReader& -ConfigReader::instance() -{ - static ConfigReader instance; - return instance; -} - -void -ConfigReader::setFile(boost::filesystem::path p) -{ - if (_config != nullptr) - delete _config; - - _config = new libconfig::Config(); - - _config->readFile(p.string().c_str()); -} - -std::string -ConfigReader::getString(std::string setting, std::string def) -{ - try { - return _config->lookup(setting); - } - catch (std::exception &e) - { - return def; - } -} - -unsigned long -ConfigReader::getULong(std::string setting, unsigned long def) -{ - try { - return static_cast(_config->lookup(setting)); - } - catch (...) - { - return def; - } -} - -long -ConfigReader::getLong(std::string setting, long def) -{ - try { - return _config->lookup(setting); - } - catch (...) - { - return def; - } -} - -bool -ConfigReader::getBool(std::string setting, bool def) -{ - try { - return _config->lookup(setting); - } - catch (...) - { - return def; - } -} - diff --git a/src/config/ConfigReader.hpp b/src/config/ConfigReader.hpp deleted file mode 100644 index c503053a..00000000 --- a/src/config/ConfigReader.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#ifndef CONFIG_READER_HPP -#define CONFIG_READER_HPP - -#include -#include - -class ConfigReader -{ - public: - - ConfigReader(const ConfigReader&) = delete; - ConfigReader& operator=(const ConfigReader&) = delete; - - static ConfigReader& instance(); - - void setFile(boost::filesystem::path p); - - /* Default values are returned in case of setting not found */ - std::string getString(std::string setting, std::string def = ""); - unsigned long getULong(std::string setting, unsigned long def = 0); - long getLong(std::string setting, long def = 0); - bool getBool(std::string setting, bool def = false); - - private: - - ConfigReader(); - - libconfig::Config *_config; -}; - -#endif diff --git a/src/cover/CoverArt.cpp b/src/cover/CoverArt.cpp deleted file mode 100644 index 35b2c991..00000000 --- a/src/cover/CoverArt.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#include -#include -#include -#include -#include -#include - -#include "logger/Logger.hpp" - -#include "CoverArt.hpp" - -namespace CoverArt { - -bool -CoverArt::scale(std::size_t size) -{ - bool res = false; - - if (!size) - return false; - - try { - - boost::gil::rgb8_image_t source; - boost::gil::rgb8_image_t dest(size, size); - - // Read source - { - std::istringstream iss( std::string(_data.begin(), _data.end())); - boost::gil::read_image(iss, source, boost::gil::jpeg_tag()); - } - - if (source.width() == static_cast(size) - && source.height() == static_cast(size)) - return true; - - // Resize - boost::gil::resize_view(boost::gil::const_view(source), - boost::gil::view(dest), - boost::gil::bilinear_sampler()); - - // Output to dest - { - std::ostringstream oss; - boost::gil::write_view(oss, boost::gil::const_view(dest), boost::gil::jpeg_tag()); - - std::string output = oss.str(); - _data.assign(output.begin(), output.end()); - } - - res = true; - } - catch(std::exception& e) - { - LMS_LOG(MOD_COVER, SEV_ERROR) << "Caught exception: " << e.what(); - } - - return res; -} - -} // namespace CoverArt diff --git a/src/cover/CoverArt.hpp b/src/cover/CoverArt.hpp deleted file mode 100644 index 4594841c..00000000 --- a/src/cover/CoverArt.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS 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 3 of the License, or - * (at your option) any later version. - * - * LMS 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 LMS. If not, see . - */ - -#ifndef COVER_ART_HPP -#define COVER_ART_HPP - -#include -#include - - -namespace CoverArt -{ - -class CoverArt -{ - public: - typedef std::vector data_type; - - CoverArt() {} - CoverArt(const std::string& mime, const data_type& data) : _mimeType(mime), _data(data) {} - - const std::string& getMimeType() const { return _mimeType; } - const data_type& getData() const { return _data; } - - void setMimeType(const std::string& mimeType) { _mimeType = mimeType;} - void setData(const data_type& data) { _data = data; } - - bool scale(std::size_t size); - - private: - - std::string _mimeType; - data_type _data; -}; - - -} // namespace CoverArt - -#endif - diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp index 695ddc3e..738d04f3 100644 --- a/src/cover/CoverArtGrabber.cpp +++ b/src/cover/CoverArtGrabber.cpp @@ -18,26 +18,12 @@ */ #include "logger/Logger.hpp" -#include "config/ConfigReader.hpp" -#include "av/InputFormatContext.hpp" +#include "av/AvInfo.hpp" #include "CoverArtGrabber.hpp" - namespace { -std::vector splitStrings(const std::string& source) -{ - std::vector res; - std::istringstream oss(source); - - std::string str; - while(oss >> str) - res.push_back(str); - - return res; -} - bool isFileSupported(const boost::filesystem::path& file, const std::vector extensions) { @@ -56,7 +42,6 @@ isFileSupported(const boost::filesystem::path& file, const std::vector +getFromAvMediaFile(const Av::MediaFile& input, std::size_t nbMaxCovers) { - for (const std::string& extension : splitStrings( ConfigReader::instance().getString("main.cover.file_extensions"))) - _fileExtensions.push_back("." + extension); + std::vector res; - _maxFileSize = ConfigReader::instance().getULong("main.cover.file_max_size"); -} - -std::vector -Grabber::getFromInputFormatContext(const Av::InputFormatContext& input, std::size_t nbMaxCovers) const -{ - std::vector res; - - try + for (Av::Picture& picture : input.getAttachedPictures(nbMaxCovers)) { - std::vector pictures = input.getPictures(nbMaxCovers); + Image::Image image; - for (Av::Picture& picture : pictures) - res.push_back( CoverArt(picture.mimeType, picture.data) ); - - } - catch(std::exception& e) - { - LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get pictures: " << e.what(); + if (image.load(picture.data)) + res.push_back( image ); + else + LMS_LOG(COVER, ERROR) << "Cannot load embedded cover file in '" << input.getPath() << "'"; } return res; } -std::vector +std::vector Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCovers) const { - std::vector res; + std::vector res; std::vector coverPathes = getCoverPaths(p, nbMaxCovers); for (auto coverPath : coverPathes) @@ -108,14 +81,12 @@ Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCov if (res.size() >= nbMaxCovers) break; - std::vector data; - std::ifstream file(coverPath.string(), std::ios::binary); - char c; - while (file.get(c)) - data.push_back(c); + Image::Image image; - // TODO handle other formats - res.push_back(CoverArt("image/jpeg", data)); + if (image.load(coverPath)) + res.push_back(image); + else + LMS_LOG(COVER, ERROR) << "Cannot load image in file '" << coverPath << "'"; } return res; @@ -143,7 +114,7 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t if (boost::filesystem::file_size(path) > _maxFileSize) { - LMS_LOG(MOD_COVER, SEV_INFO) << "Cover file '" << path << " is too big (" << boost::filesystem::file_size(path) << "), limit is " << _maxFileSize; + LMS_LOG(COVER, INFO) << "Cover file '" << path << " is too big (" << boost::filesystem::file_size(path) << "), limit is " << _maxFileSize; continue; } @@ -155,27 +126,18 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t return res; } -std::vector +std::vector Grabber::getFromTrack(const boost::filesystem::path& p, std::size_t nbMaxCovers) const { - std::vector res; + Av::MediaFile input(p); - try - { - Av::InputFormatContext input(p); - - res = getFromInputFormatContext(input, nbMaxCovers); - - } - catch(std::exception& e) - { - LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get covers from file " << p << ": " << e.what(); - } - - return res; + if (input.open()) + return getFromAvMediaFile(input, nbMaxCovers); + else + return std::vector(); } -std::vector +std::vector Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers) const { using namespace Database; @@ -184,7 +146,7 @@ Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackI Track::pointer track = Track::getById(session, trackId); if (!track) - return std::vector(); + return std::vector(); Track::CoverType coverType = track->getCoverType(); boost::filesystem::path trackPath = track->getPath(); @@ -195,17 +157,15 @@ Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackI { case Track::CoverType::Embedded: return Grabber::getFromTrack(trackPath, nbMaxCovers); - case Track::CoverType::ExternalFile: - return Grabber::getFromDirectory(trackPath.parent_path(), nbMaxCovers); case Track::CoverType::None: - return std::vector(); + return Grabber::getFromDirectory(trackPath.parent_path(), nbMaxCovers); } - return std::vector(); + return std::vector(); } -std::vector +std::vector Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers) const { using namespace Database; @@ -222,14 +182,14 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type re -1, 1 /* limit result size */); if (tracks.empty()) - return std::vector(); + return std::vector(); firstTrackPath = tracks.front()->getPath(); embeddedCover = (tracks.front()->getCoverType() == Track::CoverType::Embedded); } // First, try to get covers from the directory of the release - std::vector res = getFromDirectory( firstTrackPath.parent_path(), nbMaxCovers); + std::vector res = getFromDirectory( firstTrackPath.parent_path(), nbMaxCovers); // Fallback on the embedded cover of the first track if (res.empty() && embeddedCover) diff --git a/src/cover/CoverArtGrabber.hpp b/src/cover/CoverArtGrabber.hpp index f2dbb33e..18d53a5d 100644 --- a/src/cover/CoverArtGrabber.hpp +++ b/src/cover/CoverArtGrabber.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2015 Emeric Poupon * * This file is part of LMS. * @@ -17,16 +17,13 @@ * along with LMS. If not, see . */ -#ifndef COVER_ART_GRABBER_HPP -#define COVER_ART_GRABBER_HPP +#pragma once #include -#include "av/InputFormatContext.hpp" #include "database/Types.hpp" -#include "CoverArt.hpp" - +#include "image/Image.hpp" namespace CoverArt { @@ -38,26 +35,21 @@ class Grabber static Grabber& instance(); - void init(); - std::vector getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t nbMaxCovers = 1) const; - std::vector getFromDirectory(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const; - std::vector getFromInputFormatContext(const Av::InputFormatContext& input, std::size_t nbMaxCovers = 1) const; - std::vector getFromTrack(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const; - std::vector getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers = 1) const; - std::vector getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers = 1) const; + std::vector getFromDirectory(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const; + std::vector getFromTrack(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const; + std::vector getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers = 1) const; + std::vector getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers = 1) const; private: Grabber(); - std::vector _fileExtensions; - std::size_t _maxFileSize; - std::vector _preferredFileNames; - - + std::vector _fileExtensions + = {".jpg", ".jpeg", ".png", ".bmp"}; // TODO parametrize + std::size_t _maxFileSize = 5000000; + std::vector _preferredFileNames + = {"cover", "front"}; // TODO parametrize }; } // namespace CoverArt - -#endif diff --git a/src/database-updater/Checksum.cpp b/src/database-updater/Checksum.cpp index f4c0dec6..ad52914b 100644 --- a/src/database-updater/Checksum.cpp +++ b/src/database-updater/Checksum.cpp @@ -47,7 +47,7 @@ void computeCrc(const boost::filesystem::path& p, std::vector& cr } else { - LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Failed to open file '" << p << "'"; + LMS_LOG(DBUPDATER, ERROR) << "Failed to open file '" << p << "'"; throw std::runtime_error("Failed to open file '" + p.string() + "'" ); } diff --git a/src/database-updater/DatabaseUpdater.cpp b/src/database-updater/DatabaseUpdater.cpp index 7169a54b..f6746ca4 100644 --- a/src/database-updater/DatabaseUpdater.cpp +++ b/src/database-updater/DatabaseUpdater.cpp @@ -170,7 +170,7 @@ Updater::processNextJob(void) MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(_db.getSession()); if (settings->getManualScanRequested()) { - LMS_LOG(MOD_DBUPDATER, SEV_NOTICE) << "Manual scan requested!"; + LMS_LOG(DBUPDATER, INFO) << "Manual scan requested!"; scheduleScan( boost::posix_time::seconds(0) ); } else @@ -213,7 +213,7 @@ Updater::processNextJob(void) void Updater::scheduleScan( boost::posix_time::time_duration duration) { - LMS_LOG(MOD_DBUPDATER, SEV_NOTICE) << "Scheduling next scan in " << duration; + LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan in " << duration; _scheduleTimer.expires_from_now(duration); _scheduleTimer.async_wait( boost::bind( &Updater::process, this, boost::asio::placeholders::error) ); } @@ -221,7 +221,7 @@ Updater::scheduleScan( boost::posix_time::time_duration duration) void Updater::scheduleScan( boost::posix_time::ptime time) { - LMS_LOG(MOD_DBUPDATER, SEV_NOTICE) << "Scheduling next scan at " << time; + LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan at " << time; _scheduleTimer.expires_at(time); _scheduleTimer.async_wait( boost::bind( &Updater::process, this, boost::asio::placeholders::error) ); } @@ -246,12 +246,12 @@ Updater::process(boost::system::error_code err) for (RootDirectory rootDirectory : rootDirectories) { - LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Processing root directory '" << rootDirectory.path << "'..."; + LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory.path << "'..."; processRootDirectory(rootDirectory, stats); - LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Processing root directory '" << rootDirectory.path << "' DONE"; + LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory.path << "' DONE"; } - LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Scan complete. Changes = " << stats.nbChanges() << ", Errors = " << stats.nbScanErrors; + LMS_LOG(DBUPDATER, INFO) << "Scan complete. Changes = " << stats.nbChanges() << ", Errors = " << stats.nbScanErrors; // Update database stats boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); @@ -382,27 +382,13 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) Wt::Dbo::Transaction transaction(_db.getSession()); - // Skip file if last write is the same Wt::Dbo::ptr track = Track::getByPath(_db.getSession(), file); - // if the file is the same and embeds covers, no need to update - if (track && track->getLastWriteTime() == lastWriteTime - && (track->getCoverType() == Database::Track::CoverType::Embedded)) - return; - - // Check for external covers - std::vector externalCovers = CoverArt::Grabber::instance().getCoverPaths(file.parent_path()); + // Skip file if last write is the same if (track && track->getLastWriteTime() == lastWriteTime) - { - // no change since last time we updated - // Skip only if no external covers has to be set - if (((track->getCoverType() == Database::Track::CoverType::None) && externalCovers.empty()) - || ((track->getCoverType() == Database::Track::CoverType::ExternalFile) && !externalCovers.empty())) return; - } MetaData::Items items; - if (!_metadataParser.parse(file, items)) return; @@ -412,7 +398,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) if (items.find(MetaData::Type::AudioStreams) == items.end() || boost::any_cast >(items[MetaData::Type::AudioStreams]).empty()) { - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Skipped '" << file << "' (no audio stream found)"; + LMS_LOG(DBUPDATER, DEBUG) << "Skipped '" << file << "' (no audio stream found)"; // If Track exists here, delete it! if (track) { @@ -424,7 +410,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) if (items.find(MetaData::Type::Duration) == items.end() || boost::any_cast(items[MetaData::Type::Duration]).total_seconds() <= 0) { - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Skipped '" << file << "' (no duration or duration <= 0)"; + LMS_LOG(DBUPDATER, DEBUG) << "Skipped '" << file << "' (no duration or duration <= 0)"; // If Track exists here, delete it! if (track) { @@ -497,12 +483,12 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) { // Create a new song track = Track::create(_db.getSession(), file); - LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Adding '" << file << "'"; + LMS_LOG(DBUPDATER, INFO) << "Adding '" << file << "'"; stats.nbAdded++; } else { - LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Updating '" << file << "'"; + LMS_LOG(DBUPDATER, INFO) << "Updating '" << file << "'"; stats.nbModified++; } @@ -551,19 +537,14 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) { bool hasCover = boost::any_cast(items[MetaData::Type::HasCover]); - if (hasCover) - track.modify()->setCoverType( Track::CoverType::Embedded ); - else if (!externalCovers.empty()) - track.modify()->setCoverType( Track::CoverType::ExternalFile ); - else - track.modify()->setCoverType( Track::CoverType::None); + track.modify()->setCoverType( hasCover ? Track::CoverType::Embedded : Track::CoverType::None ); } transaction.commit(); } catch( std::exception& e ) { - LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Exception while parsing audio file : '" << file << "': '" << e.what() << "' => skipping!"; + LMS_LOG(DBUPDATER, ERROR) << "Exception while parsing audio file : '" << file << "': '" << e.what() << "' => skipping!"; stats.nbRemoved++; } } @@ -618,7 +599,7 @@ Updater::checkFile(const boost::filesystem::path& p, const std::vector rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Audio); - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking tracks..."; + LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks..."; auto tracks = Track::getAll(_db.getSession()); for (auto track : tracks) { @@ -678,45 +659,45 @@ Updater::checkAudioFiles( Stats& stats ) } // Now process orphan Genre (no track) - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Genres..."; + LMS_LOG(DBUPDATER, DEBUG) << "Checking Genres..."; auto genres = Genre::getAll(_db.getSession()); for (auto genre : genres) { if (genre->getTracks().size() == 0) { - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Removing orphan genre '" << genre->getName() << "'"; + LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan genre '" << genre->getName() << "'"; genre.remove(); } } - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking artists..."; + LMS_LOG(DBUPDATER, DEBUG) << "Checking artists..."; auto artists = Artist::getAllOrphans(_db.getSession()); for (auto artist : artists) { - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Removing orphan artist '" << artist->getName() << "'"; + LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan artist '" << artist->getName() << "'"; artist.remove(); } - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking releases..."; + LMS_LOG(DBUPDATER, DEBUG) << "Checking releases..."; auto releases = Release::getAllOrphans(_db.getSession()); for (auto release : releases) { - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Removing orphan release '" << release->getName() << "'"; + LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan release '" << release->getName() << "'"; release.remove(); } - LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Check audio files done!"; + LMS_LOG(DBUPDATER, INFO) << "Check audio files done!"; } void Updater::checkVideoFiles( Stats& stats ) { - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking video files..."; + LMS_LOG(DBUPDATER, DEBUG) << "Checking video files..."; Wt::Dbo::Transaction transaction(_db.getSession()); std::vector rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Video); - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking videos..."; + LMS_LOG(DBUPDATER, DEBUG) << "Checking videos..."; typedef Wt::Dbo::collection< Wt::Dbo::ptr