Merge branch 'simplification' into develop

Conflicts:
	README.md
This commit is contained in:
epoupon
2015-09-24 13:13:43 +02:00
188 changed files with 1557 additions and 22259 deletions
+20
View File
@@ -3,3 +3,23 @@ AUTOMAKE_OPTIONS = dist-bzip2 no-dist-gzip
SUBDIRS = test src 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
+17 -31
View File
@@ -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 - Winamp-like interface, suited for large databases
- Audio/Video transcode for maximum interoperability and low bandwith requirements - Audio/Video transcode for maximum interoperability and low bandwith requirements
- User management - 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. 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 ## Dependencies
### Debian or Ubuntu packages ### Debian or Ubuntu packages
```sh ```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 ## Build
```sh ```sh
$ autoreconf -vfi $ autoreconf -vfi
$ mkdir build $ mkdir build
$ cd build $ cd build
$ ../configure $ ../configure --prefix=/usr
``` ```
configure will complain if a mandatory library is missing. 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 ```sh
$ make $ make -j 4
``` ```
## Setting up SSL materials ## Install
This is required by the SSL server and the web server. ```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. Here is just a self signed certificate example, you could do use a CA if you want.
Generate a dh file: Generate a dh file:
```sh ```sh
$ openssl dhparam -out dh2048.pem 2048 $ openssl dhparam -out dh2048.pem 2048
``` ```
Generate a self signed certificate: Generate a self signed certificate:
```sh ```sh
$ openssl req -x509 -out cert.pem -keyout privkey.pem -newkey rsa:4096 $ openssl req -x509 -out cert.pem -keyout privkey.pem -newkey rsa:4096
``` ```
## Running
In the build directory:
```sh ```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. Depending on your SSL parameters, you may be asked for the PEM passphrase to unlock the private key.
## Credits ## 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/) - Wt (http://www.webtoolkit.eu/)
- bootstrap3 (http://getbootstrap.com/) - bootstrap3 (http://getbootstrap.com/)
- libav project (https://www.libav.org/). - libav project (https://www.libav.org/).
- Protocol Buffers (https://github.com/google/protobuf/)
73,1 Bot 73,1 Bot
+4 -16
View File
@@ -3,8 +3,6 @@
- Make the feature - Make the feature
[Cover] [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 - Handle preferred cover file names
- Implement a cache and a grabber from some web service (mandatory for artists) - Implement a cache and a grabber from some web service (mandatory for artists)
@@ -25,18 +23,12 @@
[Users] [Users]
- Handle login lifetime? - Handle login lifetime?
- Limit multi login from UI and remote interfaces (limit per interface is acceptable) - Use the WServer::post method to kick users already logged in?
- Limit multi login from UI and API interfaces (limit per interface is acceptable)
[ServiceManager]
- Rework the whole start/stop/try/cach/thread/interrupts things
- Use our own WIOService
[Services] [Services]
- consider adding support for external web servers (new FCGI service?) - 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] [UI]
- handle internationalization - handle internationalization
[Settings] [Settings]
@@ -74,12 +66,8 @@
- Make a dedicated audio layout in mobile environment (keep the same layout for videos?) - Make a dedicated audio layout in mobile environment (keep the same layout for videos?)
- Handle internal path - Handle internal path
[Lms API]
- Implement partial text search options in the GetXXX messages
- Make it REST ?
[REST API] [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] [Logs]
- Capture transcoder log output to get more information on errors - Capture transcoder log output to get more information on errors?
-63
View File
@@ -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";
}
}
+14 -64
View File
@@ -8,56 +8,26 @@ AC_LANG_CPLUSPLUS
# Checks for programs # Checks for programs
AC_PROG_CXX 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], AM_CONDITIONAL([VIDEO], [test x$enable_video = xyes])
[Build with the lmsapi protocol support. Defaults to yes.])], AS_IF([test x$enable_video = xyes], [AC_DEFINE(HAVE_VIDEO, [1], [Enable Video support])])
[enable_lmsapi="$enableval"],
[enable_lmsapi="yes"])
# ProtoBuf compiler. PKG_CHECK_MODULES(IMAGEMAGICKXX, "ImageMagick++", [ HAVE_IMAGEMAGICKXX=yes ], [ ])
# First, specify with --with-protoc=/path/of/protoc. if test -n "$HAVE_IMAGEMAGICKXX"; then
# Or, specify with env variable PROTOC. MAGICKXX_CFLAGS="$IMAGEMAGICKXX_CFLAGS"
# If neither of the above, find it in the path. MAGICKXX_LIBS="$IMAGEMAGICKXX_LIBS"
AS_IF([test "$enable_lmsapi" = "yes"], fi
[AC_ARG_WITH([protoc], AC_SUBST(MAGICKXX_CFLAGS)
[AS_HELP_STRING([--with-protoc=/path/of/protoc], AC_SUBST(MAGICKXX_LIBS)
[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])
AM_CONDITIONAL([LMSAPI], [test x$lmsapi = xtrue]) AC_CHECK_HEADERS([Wt/WApplication],
AS_IF([test x$lmsapi = xtrue], [AC_DEFINE(HAVE_LMSAPI, [1], [Enable LMS API support])])
AC_CHECK_HEADERS([Wt/WApplication jpeglib.h],
[], [],
[AC_MSG_ERROR([Header not found or unusable !])]) [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], AC_CHECK_LIB([avutil],
[av_free], [av_free],
, ,
@@ -73,16 +43,6 @@ AC_CHECK_LIB([avformat],
, ,
[AC_MSG_ERROR([libavformat not found!])]) [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], AC_CHECK_LIB([boost_system],
[main], [main],
, ,
@@ -118,16 +78,6 @@ AC_CHECK_LIB( [boost_iostreams],
, ,
[AC_MSG_ERROR([libboost_iostreams not found!])]) [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 # TODO -> PARAMETRIZE THIS
AC_CHECK_LIB([wthttp], AC_CHECK_LIB([wthttp],
[main], [main],

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

-65
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
-195
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
-137
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
-69
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
+10 -61
View File
@@ -2,14 +2,8 @@ bin_PROGRAMS = lms
lms_SOURCES = \ lms_SOURCES = \
$(srcdir)/main/main.cpp \ $(srcdir)/main/main.cpp \
$(srcdir)/av/CodecContext.cpp \ $(srcdir)/av/AvInfo.cpp \
$(srcdir)/av/Common.cpp \ $(srcdir)/av/AvTranscoder.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)/cover/CoverArtGrabber.cpp \ $(srcdir)/cover/CoverArtGrabber.cpp \
$(srcdir)/database/Artist.cpp \ $(srcdir)/database/Artist.cpp \
$(srcdir)/database/DatabaseHandler.cpp \ $(srcdir)/database/DatabaseHandler.cpp \
@@ -23,16 +17,12 @@ lms_SOURCES = \
$(srcdir)/database/Video.cpp \ $(srcdir)/database/Video.cpp \
$(srcdir)/database-updater/DatabaseUpdater.cpp \ $(srcdir)/database-updater/DatabaseUpdater.cpp \
$(srcdir)/database-updater/Checksum.cpp \ $(srcdir)/database-updater/Checksum.cpp \
$(srcdir)/image/Image.cpp \
$(srcdir)/logger/Logger.cpp \ $(srcdir)/logger/Logger.cpp \
$(srcdir)/metadata/AvFormat.cpp \ $(srcdir)/metadata/AvFormat.cpp \
$(srcdir)/metadata/Utils.cpp \ $(srcdir)/metadata/Utils.cpp \
$(srcdir)/service/ServiceManager.cpp \ $(srcdir)/service/ServiceManager.cpp \
$(srcdir)/service/DatabaseUpdateService.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/LmsApplication.cpp \
$(srcdir)/ui/auth/LmsAuth.cpp \ $(srcdir)/ui/auth/LmsAuth.cpp \
$(srcdir)/ui/audio/desktop/DesktopAudio.cpp \ $(srcdir)/ui/audio/desktop/DesktopAudio.cpp \
@@ -51,10 +41,6 @@ lms_SOURCES = \
$(srcdir)/ui/common/LineEdit.cpp \ $(srcdir)/ui/common/LineEdit.cpp \
$(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \ $(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \
$(srcdir)/ui/resource/CoverResource.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/Settings.cpp \
$(srcdir)/ui/settings/SettingsAccountFormView.cpp \ $(srcdir)/ui/settings/SettingsAccountFormView.cpp \
$(srcdir)/ui/settings/SettingsAudioFormView.cpp \ $(srcdir)/ui/settings/SettingsAudioFormView.cpp \
@@ -65,51 +51,14 @@ lms_SOURCES = \
$(srcdir)/ui/settings/SettingsUserFormView.cpp \ $(srcdir)/ui/settings/SettingsUserFormView.cpp \
$(srcdir)/ui/settings/SettingsUsers.cpp $(srcdir)/ui/settings/SettingsUsers.cpp
if LMSAPI if VIDEO
lms_SOURCES += \ lms_SOURCES += \
$(srcdir)/lms-api/server/Connection.cpp \ $(srcdir)/ui/video/VideoWidget.cpp \
$(srcdir)/lms-api/server/ConnectionManager.cpp \ $(srcdir)/ui/video/VideoDatabaseWidget.cpp \
$(srcdir)/lms-api/server/AudioCollectionRequestHandler.cpp \ $(srcdir)/ui/video/VideoMediaPlayerWidget.cpp \
$(srcdir)/lms-api/server/AuthRequestHandler.cpp \ $(srcdir)/ui/video/VideoParametersDialog.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)/ $^
endif 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)
+306
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <array>
#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<char, 128> 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<int>(_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<std::string, std::string>& 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<std::string, std::string>
MediaFile::getMetaData(void)
{
if (_context == nullptr)
throw std::logic_error("inputfile '" + _p.string() + "' not open");
std::map<std::string, std::string> 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<Stream>
MediaFile::getStreams(Stream::Type type) const
{
if (_context == nullptr)
throw std::logic_error("inputfile '" + _p.string() + "' not open");
std::vector<Stream> 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<char, 256> 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<Picture>
MediaFile::getAttachedPictures(std::size_t nbMaxPictures) const
{
if (_context == nullptr)
throw std::logic_error("inputfile '" + _p.string() + "' not open");
static const std::map<int, std::string> 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<Picture> 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
+102
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
/* 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 <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/error.h>
}
#include <vector>
#include <string>
#include <cstdint>
#include <map>
#include <boost/filesystem/path.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp> //no i/o just types
namespace Av
{
void AvInit();
struct Picture
{
std::string mimeType;
std::vector<uint8_t> 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<std::string, std::string> getMetaData(void);
std::vector<Stream> getStreams(Stream::Type type) const;
int getBestStreamId(Stream::Type type) const; // -1 if failure/unknown
bool hasAttachedPictures(void) const;
std::vector<Picture> getAttachedPictures(std::size_t nbMaxPictures) const;
private:
MediaFile();
boost::filesystem::path _p;
AVFormatContext* _context;
};
} // namespace Av
#endif
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Emeric Poupon * Copyright (C) 2015 Emeric Poupon
* *
* This file is part of LMS. * This file is part of LMS.
* *
@@ -17,18 +17,29 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <sstream>
#include <boost/iostreams/stream.hpp>
#include <boost/process.hpp>
#include <boost/foreach.hpp>
#include "logger/Logger.hpp" #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? // TODO, parametrize?
const std::vector<std::string> execNames = const std::vector<std::string> execNames =
@@ -37,15 +48,14 @@ const std::vector<std::string> execNames =
"ffmpeg", "ffmpeg",
}; };
boost::mutex Transcoder::_mutex;
boost::mutex AvConvTranscoder::_mutex; boost::filesystem::path Transcoder::_avConvPath = boost::filesystem::path();
boost::filesystem::path AvConvTranscoder::_avConvPath = boost::filesystem::path();
void 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); const boost::filesystem::path p = boost::process::search_path(execName);
if (!p.empty()) if (!p.empty())
@@ -56,32 +66,35 @@ AvConvTranscoder::init()
} }
if (!_avConvPath.empty()) if (!_avConvPath.empty())
LMS_LOG(MOD_TRANSCODE, SEV_INFO) << "Using transcoder " << _avConvPath; LMS_LOG(TRANSCODE, INFO) << "Using transcoder " << _avConvPath;
else else
throw std::runtime_error("Cannot find any transcoder binary!"); throw std::runtime_error("Cannot find any transcoder binary!");
} }
//boost::filesystem::path AvConvTranscoder::_avConvPath = ""; //boost::filesystem::path Transcoder::_avConvPath = "";
AvConvTranscoder::AvConvTranscoder(const Parameters& parameters) Transcoder::Transcoder(boost::filesystem::path filePath, TranscodeParameters parameters)
: _parameters(parameters), : _filePath(filePath),
_parameters(parameters),
_outputPipe(boost::process::create_pipe()), _outputPipe(boost::process::create_pipe()),
_source(_outputPipe.source, boost::iostreams::close_handle), _source(_outputPipe.source, boost::iostreams::close_handle),
_is(_source), _is(_source),
_in(&_is), _in(&_is),
_isComplete(false), _isComplete(false)
_outputBytes(0)
{ {
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 // Launch a process to handle the conversion
boost::iostreams::file_descriptor_sink sink(_outputPipe.sink, boost::iostreams::close_handle); boost::iostreams::file_descriptor_sink sink(_outputPipe.sink, boost::iostreams::close_handle);
@@ -92,66 +105,59 @@ AvConvTranscoder::AvConvTranscoder(const Parameters& parameters)
// input Offset // input Offset
if (_parameters.getOffset().total_seconds() > 0) 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 // Input file
oss << " -i \"" << _parameters.getInputMediaFile().getPath().string() << "\""; oss << " -i " << _filePath;
// Output bitrates // Output bitrates
oss << " -b:a " << _parameters.getOutputBitrate(Stream::Audio) ; oss << " -b:a " << _parameters.getBitrate(Stream::Type::Audio) ;
if (_parameters.getOutputFormat().getType() == Format::Video) // if (_parameters.getOutputFormat().getType() == Format::Video)
oss << " -b:v " << _parameters.getOutputBitrate(Stream::Video); // oss << " -b:v " << _parameters.getOutputBitrate(Stream::Video);
// Stream mapping // Stream mapping
for (int streamId : _parameters.getSelectedStreamIds())
{ {
typedef std::map<Stream::Type, Stream::Id> StreamMap; // 0 means the first input file
oss << " -map 0:" << streamId;
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
}
} }
// Codecs and formats // Codecs and formats
switch( _parameters.getOutputFormat().getEncoding()) switch( _parameters.getEncoding())
{ {
case Format::MP3: case Encoding::MP3:
oss << " -f mp3"; oss << " -f mp3";
break; break;
case Format::OGA: case Encoding::OGA:
oss << " -acodec libvorbis -f ogg"; oss << " -acodec libvorbis -f ogg";
break; break;
case Format::OGV: case Encoding::OGV:
oss << " -acodec libvorbis -ac 2 -ar 44100 -vcodec libtheora -threads 4 -f ogg"; oss << " -acodec libvorbis -ac 2 -ar 44100 -vcodec libtheora -threads 4 -f ogg";
break; break;
case Format::WEBMA: case Encoding::WEBMA:
oss << " -codec:a libvorbis -f webm"; oss << " -codec:a libvorbis -f webm";
break; break;
case Format::WEBMV: case Encoding::WEBMV:
oss << " -acodec libvorbis -ac 2 -ar 44100 -vcodec libvpx -threads 4 -f webm"; oss << " -acodec libvorbis -ac 2 -ar 44100 -vcodec libvpx -threads 4 -f webm";
break; break;
case Format::M4A: case Encoding::M4A:
oss << " -acodec aac -f mp4 -strict experimental"; oss << " -acodec aac -f mp4 -strict experimental";
break; break;
case Format::M4V: case Encoding::M4V:
oss << " -acodec aac -strict experimental -ac 2 -ar 44100 -vcodec libx264 -f m4v"; oss << " -acodec aac -strict experimental -ac 2 -ar 44100 -vcodec libx264 -f m4v";
break; break;
case Format::FLV: case Encoding::FLV:
oss << " -acodec libmp3lame -ac 2 -ar 44100 -vcodec libx264 -f flv"; oss << " -acodec libmp3lame -ac 2 -ar 44100 -vcodec libx264 -f flv";
break; break;
case Format::FLA: case Encoding::FLA:
oss << " -acodec libmp3lame -f flv"; oss << " -acodec libmp3lame -f flv";
break; break;
default: default:
assert(0); return false;
} }
oss << " -"; // output to stdout 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 // make sure only one thread is executing this part of code
// See boost process FAQ // See boost process FAQ
@@ -167,10 +173,11 @@ AvConvTranscoder::AvConvTranscoder(const Parameters& parameters)
); );
} }
return true;
} }
void void
AvConvTranscoder::process(std::vector<unsigned char>& output, std::size_t maxSize) Transcoder::process(std::vector<unsigned char>& output, std::size_t maxSize)
{ {
std::size_t readDataSize = 0; std::size_t readDataSize = 0;
@@ -181,11 +188,10 @@ AvConvTranscoder::process(std::vector<unsigned char>& output, std::size_t maxSiz
while(readDataSize < maxSize && _in && _in.get(ch)) { while(readDataSize < maxSize && _in && _in.get(ch)) {
output.push_back(ch); output.push_back(ch);
readDataSize++; readDataSize++;
_outputBytes++; // stats
} }
if (!_in || _in.fail() || _in.eof()) { if (!_in || _in.fail() || _in.eof()) {
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Transcode complete!"; LMS_LOG(TRANSCODE, DEBUG) << "Transcode complete!";
waitChild(); waitChild();
_isComplete = true; _isComplete = true;
@@ -193,9 +199,9 @@ AvConvTranscoder::process(std::vector<unsigned char>& 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()) if (_in.eof())
waitChild(); waitChild();
@@ -204,40 +210,46 @@ AvConvTranscoder::~AvConvTranscoder()
} }
void void
AvConvTranscoder::waitChild() Transcoder::waitChild()
{ {
if (_child) if (_child)
{ {
boost::system::error_code ec; 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); 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) if (ec)
LMS_LOG(MOD_TRANSCODE, SEV_ERROR) << "AvConvTranscoder::waitChild: error: " << ec.message(); LMS_LOG(TRANSCODE, ERROR) << "Transcoder::waitChild: error: " << ec.message();
_child.reset(); _child.reset();
} }
} }
void void
AvConvTranscoder::killChild() Transcoder::killChild()
{ {
if (_child) if (_child)
{ {
boost::system::error_code ec; 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); 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 an error occured, force kill the child
if (ec) if (ec)
LMS_LOG(MOD_TRANSCODE, SEV_ERROR) << "AvConvTranscoder::killChild: error: " << ec.message(); LMS_LOG(TRANSCODE, ERROR) << "Transcoder::killChild: error: " << ec.message();
_child.reset(); _child.reset();
} }
} }
bool
Transcoder::isComplete(void)
{
return _isComplete;
}
} // namespace Transcode } // namespace Transcode
+122
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <map>
#include <set>
#include <iostream>
#include <boost/iostreams/stream.hpp>
#include <boost/process.hpp>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp> //no i/o just types
#include <boost/filesystem/path.hpp>
#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<int> 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<int> _selectedStreams;
std::map<Stream::Type, std::size_t> _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<unsigned char>& 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<boost::iostreams::file_descriptor_source> _is;
std::istream _in;
void waitChild();
void killChild();
std::shared_ptr<boost::process::child> _child;
static boost::filesystem::path _avConvPath;
bool _isComplete;
};
} // namespace Av
-76
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <cassert>
#include <stdexcept>
#include <iostream>
#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;
}
-56
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef CODEC_HPP__
#define CODEC_HPP__
#include <string>
#include <memory>
#include <boost/utility.hpp>
#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
-54
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdexcept>
#include <iostream>
#include <iomanip>
#include <boost/array.hpp>
#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<char, 256> buf;
avcodec_string(buf.data(), buf.size(), _codecContext, 0);
return std::string(buf.data());
}
} // namespace Av
-63
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef CODEC_CONTEXT_HPP
#define CODEC_CONTEXT_HPP
#include <boost/utility.hpp>
#include <iostream>
#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
-55
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <boost/array.hpp>
#include "logger/Logger.hpp"
#include "Common.hpp"
namespace Av
{
std::string
AvError::to_str(void) const
{
boost::array<char, 128> 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
-67
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <stdint.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/channel_layout.h>
#include <libavutil/mathematics.h>
}
#include <string>
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
-50
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include "Dictionary.hpp"
namespace Av
{
Dictionary::Dictionary(AVDictionary* dictionary)
: _dictionary(dictionary)
{
}
void
Dictionary::get(std::map<std::string, std::string>& 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
-47
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef AV_DICTIONARY_HPP
#define AV_DICTIONARY_HPP
#include <map>
#include "Common.hpp"
namespace Av
{
class Dictionary
{
public:
Dictionary(AVDictionary* dictionary);
// Get all
void get(std::map<std::string, std::string>& entries);
// get a single entry
std::string get(std::string key);
private:
AVDictionary* _dictionary;
};
} // namespace Av
#endif
-35
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include "FormatContext.hpp"
namespace Av
{
FormatContext::FormatContext()
: _context(nullptr)
{
}
FormatContext::~FormatContext()
{
}
} // namespace Av
-176
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdexcept>
#include <iostream>
#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<Stream>
InputFormatContext::getStreams(void)
{
std::vector<Stream> 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<int>(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<Picture>
InputFormatContext::getPictures(std::size_t nbMaxPictures) const
{
static const std::map<int, std::string> 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<Picture> 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
-72
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INPUT_FORMAT_CONTEXT_HPP
#define INPUT_FORMAT_CONTEXT_HPP
#include <vector>
#include <boost/filesystem.hpp>
#include "FormatContext.hpp"
#include "Stream.hpp"
#include "Dictionary.hpp"
namespace Av
{
struct Picture {
std::string mimeType;
std::vector<unsigned char> 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<Picture> getPictures(std::size_t nbMaxPictures) const;
// Get the streams
std::vector<Stream> getStreams(void);
bool getBestStreamIdx(enum AVMediaType type, Stream::Idx& idx);
std::size_t getDurationSecs() const;
private:
boost::filesystem::path _path;
};
} // namespace av
#endif
-51
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <cassert>
#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
-55
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#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
-98
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <sstream>
#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<unsigned int>(_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;
}
}
-50
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef CONFIG_READER_HPP
#define CONFIG_READER_HPP
#include <boost/filesystem.hpp>
#include <libconfig.h++>
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
-80
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <boost/gil/image.hpp>
#include <boost/gil/typedefs.hpp>
#include <boost/gil/extension/io/jpeg_io.hpp>
#include <boost/gil/extension/numeric/sampler.hpp>
#include <boost/gil/extension/numeric/resample.hpp>
#include <boost/gil/extension/io_new/jpeg_all.hpp>
#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<int>(size)
&& source.height() == static_cast<int>(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
-56
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef COVER_ART_HPP
#define COVER_ART_HPP
#include <vector>
#include <string>
namespace CoverArt
{
class CoverArt
{
public:
typedef std::vector<unsigned char> 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
+31 -71
View File
@@ -18,26 +18,12 @@
*/ */
#include "logger/Logger.hpp" #include "logger/Logger.hpp"
#include "config/ConfigReader.hpp" #include "av/AvInfo.hpp"
#include "av/InputFormatContext.hpp"
#include "CoverArtGrabber.hpp" #include "CoverArtGrabber.hpp"
namespace { namespace {
std::vector<std::string> splitStrings(const std::string& source)
{
std::vector<std::string> res;
std::istringstream oss(source);
std::string str;
while(oss >> str)
res.push_back(str);
return res;
}
bool bool
isFileSupported(const boost::filesystem::path& file, const std::vector<boost::filesystem::path> extensions) isFileSupported(const boost::filesystem::path& file, const std::vector<boost::filesystem::path> extensions)
{ {
@@ -56,7 +42,6 @@ isFileSupported(const boost::filesystem::path& file, const std::vector<boost::fi
namespace CoverArt { namespace CoverArt {
Grabber::Grabber() Grabber::Grabber()
: _maxFileSize(0)
{ {
} }
@@ -67,40 +52,28 @@ Grabber::instance()
return instance; return instance;
} }
void static std::vector<Image::Image>
Grabber::init() getFromAvMediaFile(const Av::MediaFile& input, std::size_t nbMaxCovers)
{ {
for (const std::string& extension : splitStrings( ConfigReader::instance().getString("main.cover.file_extensions"))) std::vector<Image::Image> res;
_fileExtensions.push_back("." + extension);
_maxFileSize = ConfigReader::instance().getULong("main.cover.file_max_size"); for (Av::Picture& picture : input.getAttachedPictures(nbMaxCovers))
}
std::vector<CoverArt>
Grabber::getFromInputFormatContext(const Av::InputFormatContext& input, std::size_t nbMaxCovers) const
{
std::vector<CoverArt> res;
try
{ {
std::vector<Av::Picture> pictures = input.getPictures(nbMaxCovers); Image::Image image;
for (Av::Picture& picture : pictures) if (image.load(picture.data))
res.push_back( CoverArt(picture.mimeType, picture.data) ); res.push_back( image );
else
} LMS_LOG(COVER, ERROR) << "Cannot load embedded cover file in '" << input.getPath() << "'";
catch(std::exception& e)
{
LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get pictures: " << e.what();
} }
return res; return res;
} }
std::vector<CoverArt> std::vector<Image::Image>
Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCovers) const Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCovers) const
{ {
std::vector<CoverArt> res; std::vector<Image::Image> res;
std::vector<boost::filesystem::path> coverPathes = getCoverPaths(p, nbMaxCovers); std::vector<boost::filesystem::path> coverPathes = getCoverPaths(p, nbMaxCovers);
for (auto coverPath : coverPathes) for (auto coverPath : coverPathes)
@@ -108,14 +81,12 @@ Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCov
if (res.size() >= nbMaxCovers) if (res.size() >= nbMaxCovers)
break; break;
std::vector<unsigned char> data; Image::Image image;
std::ifstream file(coverPath.string(), std::ios::binary);
char c;
while (file.get(c))
data.push_back(c);
// TODO handle other formats if (image.load(coverPath))
res.push_back(CoverArt("image/jpeg", data)); res.push_back(image);
else
LMS_LOG(COVER, ERROR) << "Cannot load image in file '" << coverPath << "'";
} }
return res; return res;
@@ -143,7 +114,7 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t
if (boost::filesystem::file_size(path) > _maxFileSize) 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; continue;
} }
@@ -155,27 +126,18 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t
return res; return res;
} }
std::vector<CoverArt> std::vector<Image::Image>
Grabber::getFromTrack(const boost::filesystem::path& p, std::size_t nbMaxCovers) const Grabber::getFromTrack(const boost::filesystem::path& p, std::size_t nbMaxCovers) const
{ {
std::vector<CoverArt> res; Av::MediaFile input(p);
try if (input.open())
{ return getFromAvMediaFile(input, nbMaxCovers);
Av::InputFormatContext input(p); else
return std::vector<Image::Image>();
res = getFromInputFormatContext(input, nbMaxCovers);
}
catch(std::exception& e)
{
LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get covers from file " << p << ": " << e.what();
}
return res;
} }
std::vector<CoverArt> std::vector<Image::Image>
Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers) const Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers) const
{ {
using namespace Database; 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); Track::pointer track = Track::getById(session, trackId);
if (!track) if (!track)
return std::vector<CoverArt>(); return std::vector<Image::Image>();
Track::CoverType coverType = track->getCoverType(); Track::CoverType coverType = track->getCoverType();
boost::filesystem::path trackPath = track->getPath(); 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: case Track::CoverType::Embedded:
return Grabber::getFromTrack(trackPath, nbMaxCovers); return Grabber::getFromTrack(trackPath, nbMaxCovers);
case Track::CoverType::ExternalFile:
return Grabber::getFromDirectory(trackPath.parent_path(), nbMaxCovers);
case Track::CoverType::None: case Track::CoverType::None:
return std::vector<CoverArt>(); return Grabber::getFromDirectory(trackPath.parent_path(), nbMaxCovers);
} }
return std::vector<CoverArt>(); return std::vector<Image::Image>();
} }
std::vector<CoverArt> std::vector<Image::Image>
Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers) const Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers) const
{ {
using namespace Database; using namespace Database;
@@ -222,14 +182,14 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type re
-1, 1 /* limit result size */); -1, 1 /* limit result size */);
if (tracks.empty()) if (tracks.empty())
return std::vector<CoverArt>(); return std::vector<Image::Image>();
firstTrackPath = tracks.front()->getPath(); firstTrackPath = tracks.front()->getPath();
embeddedCover = (tracks.front()->getCoverType() == Track::CoverType::Embedded); embeddedCover = (tracks.front()->getCoverType() == Track::CoverType::Embedded);
} }
// First, try to get covers from the directory of the release // First, try to get covers from the directory of the release
std::vector<CoverArt> res = getFromDirectory( firstTrackPath.parent_path(), nbMaxCovers); std::vector<Image::Image> res = getFromDirectory( firstTrackPath.parent_path(), nbMaxCovers);
// Fallback on the embedded cover of the first track // Fallback on the embedded cover of the first track
if (res.empty() && embeddedCover) if (res.empty() && embeddedCover)
+12 -20
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Emeric Poupon * Copyright (C) 2015 Emeric Poupon
* *
* This file is part of LMS. * This file is part of LMS.
* *
@@ -17,16 +17,13 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef COVER_ART_GRABBER_HPP #pragma once
#define COVER_ART_GRABBER_HPP
#include <vector> #include <vector>
#include "av/InputFormatContext.hpp"
#include "database/Types.hpp" #include "database/Types.hpp"
#include "CoverArt.hpp" #include "image/Image.hpp"
namespace CoverArt { namespace CoverArt {
@@ -38,26 +35,21 @@ class Grabber
static Grabber& instance(); static Grabber& instance();
void init();
std::vector<boost::filesystem::path> getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t nbMaxCovers = 1) const; std::vector<boost::filesystem::path> getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t nbMaxCovers = 1) const;
std::vector<CoverArt> getFromDirectory(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const; std::vector<Image::Image> getFromDirectory(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const;
std::vector<CoverArt> getFromInputFormatContext(const Av::InputFormatContext& input, std::size_t nbMaxCovers = 1) const; std::vector<Image::Image> getFromTrack(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const;
std::vector<CoverArt> getFromTrack(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const; std::vector<Image::Image> getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers = 1) const;
std::vector<CoverArt> getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers = 1) const; std::vector<Image::Image> getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers = 1) const;
std::vector<CoverArt> getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers = 1) const;
private: private:
Grabber(); Grabber();
std::vector<boost::filesystem::path> _fileExtensions; std::vector<boost::filesystem::path> _fileExtensions
std::size_t _maxFileSize; = {".jpg", ".jpeg", ".png", ".bmp"}; // TODO parametrize
std::vector<boost::filesystem::path> _preferredFileNames; std::size_t _maxFileSize = 5000000;
std::vector<boost::filesystem::path> _preferredFileNames
= {"cover", "front"}; // TODO parametrize
}; };
} // namespace CoverArt } // namespace CoverArt
#endif
+1 -1
View File
@@ -47,7 +47,7 @@ void computeCrc(const boost::filesystem::path& p, std::vector<unsigned char>& cr
} }
else 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() + "'" ); throw std::runtime_error("Failed to open file '" + p.string() + "'" );
} }
+34 -53
View File
@@ -170,7 +170,7 @@ Updater::processNextJob(void)
MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(_db.getSession()); MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(_db.getSession());
if (settings->getManualScanRequested()) { 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) ); scheduleScan( boost::posix_time::seconds(0) );
} }
else else
@@ -213,7 +213,7 @@ Updater::processNextJob(void)
void void
Updater::scheduleScan( boost::posix_time::time_duration duration) 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.expires_from_now(duration);
_scheduleTimer.async_wait( boost::bind( &Updater::process, this, boost::asio::placeholders::error) ); _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 void
Updater::scheduleScan( boost::posix_time::ptime time) 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.expires_at(time);
_scheduleTimer.async_wait( boost::bind( &Updater::process, this, boost::asio::placeholders::error) ); _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) 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); 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 // Update database stats
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); 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()); Wt::Dbo::Transaction transaction(_db.getSession());
// Skip file if last write is the same
Wt::Dbo::ptr<Track> track = Track::getByPath(_db.getSession(), file); Wt::Dbo::ptr<Track> track = Track::getByPath(_db.getSession(), file);
// if the file is the same and embeds covers, no need to update // Skip file if last write is the same
if (track && track->getLastWriteTime() == lastWriteTime
&& (track->getCoverType() == Database::Track::CoverType::Embedded))
return;
// Check for external covers
std::vector<boost::filesystem::path> externalCovers = CoverArt::Grabber::instance().getCoverPaths(file.parent_path());
if (track && track->getLastWriteTime() == lastWriteTime) 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; return;
}
MetaData::Items items; MetaData::Items items;
if (!_metadataParser.parse(file, items)) if (!_metadataParser.parse(file, items))
return; return;
@@ -412,7 +398,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
if (items.find(MetaData::Type::AudioStreams) == items.end() if (items.find(MetaData::Type::AudioStreams) == items.end()
|| boost::any_cast<std::vector<MetaData::AudioStream> >(items[MetaData::Type::AudioStreams]).empty()) || boost::any_cast<std::vector<MetaData::AudioStream> >(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 exists here, delete it!
if (track) { if (track) {
@@ -424,7 +410,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
if (items.find(MetaData::Type::Duration) == items.end() if (items.find(MetaData::Type::Duration) == items.end()
|| boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]).total_seconds() <= 0) || boost::any_cast<boost::posix_time::time_duration>(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 exists here, delete it!
if (track) { if (track) {
@@ -497,12 +483,12 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
{ {
// Create a new song // Create a new song
track = Track::create(_db.getSession(), file); track = Track::create(_db.getSession(), file);
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Adding '" << file << "'"; LMS_LOG(DBUPDATER, INFO) << "Adding '" << file << "'";
stats.nbAdded++; stats.nbAdded++;
} }
else else
{ {
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Updating '" << file << "'"; LMS_LOG(DBUPDATER, INFO) << "Updating '" << file << "'";
stats.nbModified++; stats.nbModified++;
} }
@@ -551,19 +537,14 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
{ {
bool hasCover = boost::any_cast<bool>(items[MetaData::Type::HasCover]); bool hasCover = boost::any_cast<bool>(items[MetaData::Type::HasCover]);
if (hasCover) track.modify()->setCoverType( hasCover ? Track::CoverType::Embedded : Track::CoverType::None );
track.modify()->setCoverType( Track::CoverType::Embedded );
else if (!externalCovers.empty())
track.modify()->setCoverType( Track::CoverType::ExternalFile );
else
track.modify()->setCoverType( Track::CoverType::None);
} }
transaction.commit(); transaction.commit();
} }
catch( std::exception& e ) 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++; stats.nbRemoved++;
} }
} }
@@ -618,7 +599,7 @@ Updater::checkFile(const boost::filesystem::path& p, const std::vector<boost::fi
if (!boost::filesystem::exists( p ) if (!boost::filesystem::exists( p )
|| !boost::filesystem::is_regular( p ) ) || !boost::filesystem::is_regular( p ) )
{ {
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Missing file '" << p << "'"; LMS_LOG(DBUPDATER, INFO) << "Missing file '" << p << "'";
status = false; status = false;
} }
else else
@@ -636,12 +617,12 @@ Updater::checkFile(const boost::filesystem::path& p, const std::vector<boost::fi
if (!foundRoot) if (!foundRoot)
{ {
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Out of root file '" << p << "'"; LMS_LOG(DBUPDATER, INFO) << "Out of root file '" << p << "'";
status = false; status = false;
} }
else if (!isFileSupported(p, extensions)) else if (!isFileSupported(p, extensions))
{ {
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "File format no longer supported for '" << p << "'"; LMS_LOG(DBUPDATER, INFO) << "File format no longer supported for '" << p << "'";
status = false; status = false;
} }
} }
@@ -651,7 +632,7 @@ Updater::checkFile(const boost::filesystem::path& p, const std::vector<boost::fi
} }
catch (boost::filesystem::filesystem_error& e) catch (boost::filesystem::filesystem_error& e)
{ {
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Caught exception while checking file '" << p << "': " << e.what(); LMS_LOG(DBUPDATER, ERROR) << "Caught exception while checking file '" << p << "': " << e.what();
return false; return false;
} }
@@ -661,12 +642,12 @@ void
Updater::checkAudioFiles( Stats& stats ) Updater::checkAudioFiles( Stats& stats )
{ {
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Checking audio files..."; LMS_LOG(DBUPDATER, INFO) << "Checking audio files...";
Wt::Dbo::Transaction transaction(_db.getSession()); Wt::Dbo::Transaction transaction(_db.getSession());
std::vector<boost::filesystem::path> rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Audio); std::vector<boost::filesystem::path> 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()); auto tracks = Track::getAll(_db.getSession());
for (auto track : tracks) for (auto track : tracks)
{ {
@@ -678,45 +659,45 @@ Updater::checkAudioFiles( Stats& stats )
} }
// Now process orphan Genre (no track) // 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()); auto genres = Genre::getAll(_db.getSession());
for (auto genre : genres) for (auto genre : genres)
{ {
if (genre->getTracks().size() == 0) 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(); genre.remove();
} }
} }
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking artists..."; LMS_LOG(DBUPDATER, DEBUG) << "Checking artists...";
auto artists = Artist::getAllOrphans(_db.getSession()); auto artists = Artist::getAllOrphans(_db.getSession());
for (auto artist : artists) 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(); artist.remove();
} }
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking releases..."; LMS_LOG(DBUPDATER, DEBUG) << "Checking releases...";
auto releases = Release::getAllOrphans(_db.getSession()); auto releases = Release::getAllOrphans(_db.getSession());
for (auto release : releases) 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(); release.remove();
} }
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Check audio files done!"; LMS_LOG(DBUPDATER, INFO) << "Check audio files done!";
} }
void void
Updater::checkVideoFiles( Stats& stats ) 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()); Wt::Dbo::Transaction transaction(_db.getSession());
std::vector<boost::filesystem::path> rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Video); std::vector<boost::filesystem::path> 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<Video> > Videos; typedef Wt::Dbo::collection< Wt::Dbo::ptr<Video> > Videos;
Videos videos = Video::getAll(_db.getSession()); Videos videos = Video::getAll(_db.getSession());
@@ -731,7 +712,7 @@ Updater::checkVideoFiles( Stats& stats )
} }
} }
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Check video files done!"; LMS_LOG(DBUPDATER, DEBUG) << "Check video files done!";
} }
void void
Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats) Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
@@ -757,7 +738,7 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
if (items.find(MetaData::Type::VideoStreams) == items.end() if (items.find(MetaData::Type::VideoStreams) == items.end()
|| boost::any_cast<std::vector<MetaData::VideoStream> >(items[MetaData::Type::VideoStreams]).empty()) || boost::any_cast<std::vector<MetaData::VideoStream> >(items[MetaData::Type::VideoStreams]).empty())
{ {
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Skipped '" << file << "' (no video stream found)"; LMS_LOG(DBUPDATER, ERROR) << "Skipped '" << file << "' (no video stream found)";
// If the video exists here, delete it! // If the video exists here, delete it!
if (video) { if (video) {
@@ -769,7 +750,7 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
if (items.find(MetaData::Type::Duration) == items.end() if (items.find(MetaData::Type::Duration) == items.end()
|| boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]).total_seconds() == 0) || boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]).total_seconds() == 0)
{ {
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Skipped '" << file << "' (no duration or duration 0)"; LMS_LOG(DBUPDATER, ERROR) << "Skipped '" << file << "' (no duration or duration 0)";
// If Track exists here, delete it! // If Track exists here, delete it!
if (video) { if (video) {
@@ -785,12 +766,12 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
if (!video) if (!video)
{ {
video = Video::create(_db.getSession(), file); video = Video::create(_db.getSession(), file);
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Adding '" << file << "'"; LMS_LOG(DBUPDATER, DEBUG) << "Adding '" << file << "'";
stats.nbAdded++; stats.nbAdded++;
} }
else else
{ {
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Updating '" << file << "'"; LMS_LOG(DBUPDATER, DEBUG) << "Updating '" << file << "'";
stats.nbModified++; stats.nbModified++;
} }
@@ -804,7 +785,7 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
} }
catch( std::exception& e ) catch( std::exception& e )
{ {
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Exception while parsing video file : '" << file << "': '" << e.what() << "' => skipping!"; LMS_LOG(DBUPDATER, ERROR) << "Exception while parsing video file : '" << file << "': '" << e.what() << "' => skipping!";
stats.nbScanErrors++; stats.nbScanErrors++;
} }
} }
+5 -2
View File
@@ -96,8 +96,11 @@ class Updater
Database::Handler _db; Database::Handler _db;
std::vector<boost::filesystem::path> _audioExtensions; std::vector<boost::filesystem::path> _audioExtensions
std::vector<boost::filesystem::path> _videoExtensions; = {".mp3", ".ogg", ".oga", ".aac", ".m4a", ".flac", ".wav", ".wma", ".aif", ".aiff", ".ape", ".mpc", ".shn"}; // TODO parametrize
std::vector<boost::filesystem::path> _videoExtensions
= {".flv", ".avi", ".mpg", ".mpeg", ".mp4", ".m4v", ".mkv", ".mov", ".wmv", ".ogv", ".divx", ".m2ts"}; // TODO parametrize
MetaData::Parser& _metadataParser; MetaData::Parser& _metadataParser;
+7 -2
View File
@@ -79,6 +79,7 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
{ {
_session.setConnectionPool(connectionPool); _session.setConnectionPool(connectionPool);
_session.mapClass<Database::Artist>("artist"); _session.mapClass<Database::Artist>("artist");
_session.mapClass<Database::Genre>("genre"); _session.mapClass<Database::Genre>("genre");
_session.mapClass<Database::Track>("track"); _session.mapClass<Database::Track>("track");
@@ -95,6 +96,8 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
_session.mapClass<Database::AuthInfo::AuthTokenType>("auth_token"); _session.mapClass<Database::AuthInfo::AuthTokenType>("auth_token");
try { try {
Wt::Dbo::Transaction transaction(_session);
_session.createTables(); _session.createTables();
_session.execute("CREATE INDEX artist_name_idx ON artist(name)"); _session.execute("CREATE INDEX artist_name_idx ON artist(name)");
_session.execute("CREATE INDEX genre_name_idx ON genre(name)"); _session.execute("CREATE INDEX genre_name_idx ON genre(name)");
@@ -102,7 +105,7 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
_session.execute("CREATE INDEX track_name_idx ON track(name)"); _session.execute("CREATE INDEX track_name_idx ON track(name)");
} }
catch(std::exception& e) { catch(std::exception& e) {
LMS_LOG(MOD_DB, SEV_ERROR) << "Cannot create tables: " << e.what(); LMS_LOG(DB, ERROR) << "Cannot create tables: " << e.what();
} }
_users = new UserDatabase(_session); _users = new UserDatabase(_session);
@@ -133,7 +136,7 @@ Handler::getUser(const Wt::Auth::User& authUser)
{ {
if (!authUser.isValid()) { if (!authUser.isValid()) {
LMS_LOG(MOD_DB, SEV_ERROR) << "Handler::getUser: invalid authUser"; LMS_LOG(DB, ERROR) << "Handler::getUser: invalid authUser";
return User::pointer(); return User::pointer();
} }
@@ -152,6 +155,8 @@ Handler::getUser(const Wt::Auth::User& authUser)
Wt::Dbo::SqlConnectionPool* Wt::Dbo::SqlConnectionPool*
Handler::createConnectionPool(boost::filesystem::path p) Handler::createConnectionPool(boost::filesystem::path p)
{ {
LMS_LOG(DB, INFO) << "Creating connection pool on file " << p;
Wt::Dbo::backend::Sqlite3 *connection = new Wt::Dbo::backend::Sqlite3(p.string()); Wt::Dbo::backend::Sqlite3 *connection = new Wt::Dbo::backend::Sqlite3(p.string());
connection->executeSql("pragma journal_mode=WAL"); connection->executeSql("pragma journal_mode=WAL");
-1
View File
@@ -97,7 +97,6 @@ class Track
enum class CoverType enum class CoverType
{ {
Embedded, // Contains embedded cover Embedded, // Contains embedded cover
ExternalFile, // Cover is in an external file
None, // No local cover available None, // No local cover available
}; };
+119
View File
@@ -0,0 +1,119 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "logger/Logger.hpp"
#include "Image.hpp"
namespace Image {
static
std::string format_to_magick(Format format)
{
switch (format)
{
case Format::JPEG: return "JPEG";
}
return "JPEG";
}
std::string format_to_mimeType(Format format)
{
switch (format)
{
case Format::JPEG: return "JPEG";
}
return "application/octet-stream";
}
void
init(const char *path)
{
Magick::InitializeMagick(path);
}
bool
Image::load(const std::vector<unsigned char>& rawData)
{
try
{
Magick::Blob blob(&rawData[0], rawData.size());
_image.read(blob);
return true;
}
catch (Magick::Exception& e)
{
LMS_LOG(COVER, ERROR) << "Caught Magick exception while loading raw image: " << e.what();
return false;
}
}
bool
Image::load(boost::filesystem::path p)
{
try
{
_image.read(p.string());
return true;
}
catch (Magick::Exception& e)
{
LMS_LOG(COVER, ERROR) << "Caught Magick exception while loading image from file '" << p << "': " << e.what();
return false;
}
}
bool
Image::scale(std::size_t size)
{
if (!size)
return false;
try
{
_image.resize( Magick::Geometry(size, size ) );
return true;
}
catch (Magick::Exception& e)
{
LMS_LOG(COVER, ERROR) << "Caught Magick exception: " << e.what();
return false;
}
}
void
Image::save(std::vector<unsigned char>& data, Format format) const
{
Magick::Image outputImage(_image);
outputImage.magick( format_to_magick(format));
Magick::Blob blob;
outputImage.write(&blob);
unsigned char *charBuf = (unsigned char*)blob.data();
data.assign( charBuf, charBuf + blob.length() );
}
} // namespace Image
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Emeric Poupon * Copyright (C) 2015 Emeric Poupon
* *
* This file is part of LMS. * This file is part of LMS.
* *
@@ -17,36 +17,44 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef FORMAT_CONTEXT_HPP #pragma once
#define FORMAT_CONTEXT_HPP
#include <boost/filesystem.hpp> #include <vector>
#include "Common.hpp" #include <boost/filesystem/path.hpp>
namespace Av #include <Magick++.h>
namespace Image
{ {
class FormatContext enum class Format
{ {
public: JPEG,
FormatContext();
~FormatContext();
protected:
void native(AVFormatContext* c) { _context = c;}
AVFormatContext* native() { return _context; }
const AVFormatContext* native() const { return _context; }
private:
AVFormatContext* _context;
}; };
} // namespace Av std::string format_to_mimeType(Format format);
#endif void init(const char *path);
class Image
{
public:
// input
bool load(const std::vector<unsigned char>& rawData);
bool load(boost::filesystem::path p);
// Operations
bool scale(std::size_t size);
// output
void save(std::vector<unsigned char>& rawData, Format format) const;
private:
Magick::Image _image;
};
} // namespace Image
-105
View File
@@ -1,105 +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 <http://www.gnu.org/licenses/>.
*/
#ifndef REMOTE_HEADER_HPP
#define REMOTE_HEADER_HPP
#include <iomanip>
namespace LmsAPI
{
class Header
{
public:
static const std::size_t size = 8; // HeaderSize
static const std::size_t max_data_size = 65536*64 - size;
Header() : _dataSize(0) {}
void setDataSize(std::size_t size) { _dataSize = size; }
std::size_t getDataSize(void) const {return _dataSize;}
bool from_istream(std::istream &is)
{
std::array<unsigned char, size> buffer;
bool res = is.read(reinterpret_cast<char*>(buffer.data()), buffer.size());
if (res && is.gcount() == buffer.size())
return from_buffer(buffer);
else
return false;
}
bool from_buffer(const std::array<unsigned char, size>& buffer)
{
if (decode32(&buffer[0]) != _magic)
{
return false;
}
else
{
_dataSize = decode32(&buffer[4]);
return _dataSize <= max_data_size;
}
}
/* void to_ostream(std::ostream& os)
{
}*/
void to_buffer(std::array<unsigned char, size>& buffer) const
{
encode32(_magic, &buffer[0]);
encode32(_dataSize, &buffer[4]);
}
private:
static uint32_t decode32(const unsigned char* data)
{
return (static_cast<uint32_t>(data[0]) << 24)
+ (static_cast<uint32_t>(data[1]) << 16)
+ (static_cast<uint32_t>(data[2]) << 8)
+ (static_cast<uint32_t>(data[3]));
}
static void encode32(uint32_t value, unsigned char* data)
{
data[0] = (value >> 24) & 0xFF;
data[1] = (value >> 16) & 0xFF;
data[2] = (value >> 8) & 0xFF;
data[3] = (value) & 0xFF;
}
static const uint32_t _magic = 0xdeadbeef;
uint32_t _dataSize;
};
} // namespace LmsAPI
#endif
@@ -1,393 +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 <http://www.gnu.org/licenses/>.
*/
#include <algorithm> // std::min
#include <boost/locale.hpp>
#include <boost/uuid/sha1.hpp>
#include "logger/Logger.hpp"
#include "AudioCollectionRequestHandler.hpp"
#include "database/Types.hpp"
#include "cover/CoverArtGrabber.hpp"
namespace LmsAPI {
namespace Server {
using namespace Database;
static SearchFilter SearchFilterFromRequest(const AudioCollectionRequest_SearchFilter& request)
{
SearchFilter filter;
for (int id = 0; id < request.artist_id_size(); ++id)
filter.idMatch[SearchFilter::Field::Artist].push_back( request.artist_id(id) );
for (int id = 0; id < request.genre_id_size(); ++id)
filter.idMatch[SearchFilter::Field::Genre].push_back( request.genre_id(id) );
for (int id = 0; id < request.release_id_size(); ++id)
filter.idMatch[SearchFilter::Field::Release].push_back( request.release_id(id) );
for (int id = 0; id < request.track_id_size(); ++id)
filter.idMatch[SearchFilter::Field::Track].push_back( request.track_id(id) );
return filter;
}
AudioCollectionRequestHandler::AudioCollectionRequestHandler(Handler& db)
: _db(db)
{}
bool
AudioCollectionRequestHandler::process(const AudioCollectionRequest& request, AudioCollectionResponse& response)
{
bool res = false;
switch (request.type())
{
case AudioCollectionRequest::TypeGetRevision:
// No payload
res = processGetRevision(*response.mutable_revision());
if (res)
response.set_type(AudioCollectionResponse::TypeRevision);
break;
case AudioCollectionRequest::TypeGetGenreList:
if (request.has_get_genres())
{
res = processGetGenres(request.get_genres(), *response.mutable_genre_list());
if (res)
response.set_type(AudioCollectionResponse::TypeArtistList);
}
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetGenreList";
break;
case AudioCollectionRequest::TypeGetArtistList:
if (request.has_get_artists())
{
res = processGetArtists(request.get_artists(), *response.mutable_artist_list());
if (res)
response.set_type(AudioCollectionResponse::TypeArtistList);
}
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetArtistList message!";
break;
case AudioCollectionRequest::TypeGetReleaseList:
if (request.has_get_releases())
{
res = processGetReleases(request.get_releases(), *response.mutable_release_list());
if (res)
response.set_type(AudioCollectionResponse::TypeReleaseList);
}
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetReleaseList message!";
break;
case AudioCollectionRequest::TypeGetTrackList:
if (request.has_get_tracks())
{
res = processGetTracks(request.get_tracks(), *response.mutable_track_list());
if (res)
response.set_type(AudioCollectionResponse::TypeTrackList);
}
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetTrackList message!";
break;
case AudioCollectionRequest::TypeGetCoverArt:
if (request.has_get_cover_art())
res = processGetCoverArt(request.get_cover_art(), response);
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetCoverArt message!";
break;
default:
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled AudioCollectionRequest_Type = " << request.type();
}
return res;
}
bool
AudioCollectionRequestHandler::processGetGenres(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response)
{
// sanity checks
if (!request.has_batch_parameter())
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
return false;
}
std::size_t size = request.batch_parameter().size();
if (!size)
size = _maxListArtists;
size = std::min(size, _maxListArtists);
// Get filters
SearchFilter filter;
if (request.has_search_filter())
filter = SearchFilterFromRequest(request.search_filter());
Wt::Dbo::Transaction transaction( _db.getSession() );
std::vector<Genre::pointer> genres = Genre::getByFilter( _db.getSession(), filter, request.batch_parameter().offset(), static_cast<int>(size));
for (Genre::pointer genre : genres)
{
AudioCollectionResponse_Genre* addGenre = response.add_genres();
addGenre->set_id(genre.id());
addGenre->set_name( std::string( boost::locale::conv::to_utf<char>(genre->getName(), "UTF-8") ) );
}
return true;
}
bool
AudioCollectionRequestHandler::processGetArtists(const AudioCollectionRequest::GetArtistList& request, AudioCollectionResponse::ArtistList& response)
{
// sanity checks
if (!request.has_batch_parameter())
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
return false;
}
std::size_t size = request.batch_parameter().size();
if (!size)
size = _maxListArtists;
size = std::min(size, _maxListArtists);
// Get filters
SearchFilter filter;
if (request.has_search_filter())
filter = SearchFilterFromRequest(request.search_filter());
// Now fetch requested data...
Wt::Dbo::Transaction transaction( _db.getSession() );
std::vector<Artist::pointer> artists
= Artist::getByFilter(_db.getSession(), filter,
request.batch_parameter().offset(), static_cast<int>(size) );
for (Artist::pointer artist : artists)
{
AudioCollectionResponse_Artist *addArtist = response.add_artists();
addArtist->set_id(artist.id());
addArtist->set_name( std::string( boost::locale::conv::to_utf<char>(artist->getName(), "UTF-8") ) );
if (!artist->getMBID().empty())
addArtist->set_mbid(artist->getMBID());
}
return true;
}
bool
AudioCollectionRequestHandler::processGetReleases(const AudioCollectionRequest::GetReleaseList& request, AudioCollectionResponse::ReleaseList& response)
{
// sanity checks
if (!request.has_batch_parameter())
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
return false;
}
std::size_t size = request.batch_parameter().size();
if (!size)
size = _maxListReleases;
size = std::min(size, _maxListReleases);
// Get filters
SearchFilter filter;
if (request.has_search_filter())
filter = SearchFilterFromRequest(request.search_filter());
Wt::Dbo::Transaction transaction( _db.getSession() );
std::vector<Release::pointer> releases
= Release::getByFilter( _db.getSession(), filter,
request.batch_parameter().offset(), static_cast<int>(size));
for (Release::pointer release : releases)
{
AudioCollectionResponse_Release *addRelease = response.add_releases();
addRelease->set_id(release.id());
addRelease->set_name( std::string( boost::locale::conv::to_utf<char>(release->getName(), "UTF-8")));
if (!release->getMBID().empty())
addRelease->set_mbid(release->getMBID());
}
return true;
}
bool
AudioCollectionRequestHandler::processGetTracks(const AudioCollectionRequest::GetTrackList& request, AudioCollectionResponse::TrackList& response)
{
// sanity checks
if (!request.has_batch_parameter())
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
return false;
}
std::size_t size = request.batch_parameter().size();
if (!size)
size = _maxListTracks;
size = std::min(size, _maxListTracks);
// Get filters
SearchFilter filter;
if (request.has_search_filter())
filter = SearchFilterFromRequest(request.search_filter());
Wt::Dbo::Transaction transaction( _db.getSession() );
std::vector<Track::pointer> tracks
= Track::getByFilter( _db.getSession(), filter,
request.batch_parameter().offset(), static_cast<int>(size));
for (Track::pointer track : tracks)
{
AudioCollectionResponse_Track* newTrack = response.add_tracks();
newTrack->set_id(track.id());
newTrack->set_disc_number( track->getDiscNumber() );
newTrack->set_track_number( track->getTrackNumber() );
newTrack->set_artist_id( track->getArtist().id() );
newTrack->set_release_id( track->getRelease().id() );
newTrack->set_name( std::string( boost::locale::conv::to_utf<char>(track->getName(), "UTF-8") ) );
newTrack->set_duration_secs( track->getDuration().total_seconds() );
// Only send the year part of the release times
if (!track->getDate().is_special())
newTrack->set_release_date( std::to_string(track->getDate().date().year()) );
if (!track->getOriginalDate().is_special())
newTrack->set_original_release_date( std::to_string(track->getOriginalDate().date().year()) );
if (!track->getMBID().empty())
newTrack->set_mbid(track->getMBID());
for (Genre::pointer genre : track->getGenres())
newTrack->add_genre_id( genre.id() );
}
return true;
}
bool
AudioCollectionRequestHandler::processGetCoverArt(const AudioCollectionRequest::GetCoverArt& request, AudioCollectionResponse& response)
{
bool res = false;
response.set_type(AudioCollectionResponse::TypeCoverArt);
std::vector<CoverArt::CoverArt> coverArts;
switch(request.type())
{
case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease:
if (request.has_release_id())
{
coverArts = CoverArt::Grabber::instance().getFromRelease( _db.getSession(), request.release_id());
res = true;
}
break;
case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtTrack:
if (request.has_track_id())
{
coverArts = CoverArt::Grabber::instance().getFromTrack(_db.getSession(), request.track_id());
res = true;
}
break;
}
for (CoverArt::CoverArt& coverArt : coverArts)
{
AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art();
if (request.has_size())
{
std::size_t size = request.size();
if (size > _maxCoverArtSize || size == 0)
size = _maxCoverArtSize;
if (size < _minCoverArtSize)
size = _minCoverArtSize;
coverArt.scale(size);
}
cover_art->set_mime_type(coverArt.getMimeType());
cover_art->set_data( std::string( coverArt.getData().begin(), coverArt.getData().end()) );
}
return res;
}
bool
AudioCollectionRequestHandler::processGetRevision(AudioCollectionResponse::Revision& response)
{
bool res = false;
Wt::Dbo::Transaction transaction( _db.getSession() );
MediaDirectorySettings::pointer settings = MediaDirectorySettings::get( _db.getSession() );
std::string hashStr = boost::posix_time::to_iso_string(settings->getLastUpdated());
boost::uuids::detail::sha1 s;
for (const char c : hashStr)
s.process_byte(c);
unsigned int digest[5];
s.get_digest(digest);
std::ostringstream oss;
for (std::size_t i = 0; i < 5; ++i)
oss << std::hex << std::setfill('0') << std::setw(4) << digest[i];
response.set_rev(oss.str());
res = true;
return res;
}
} // namespace LmsAPI
} // namespace Server
@@ -1,61 +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 <http://www.gnu.org/licenses/>.
*/
#ifndef REMOTE_AUDIO_COLLECTION_REQUEST_HANDLER
#define REMOTE_AUDIO_COLLECTION_REQUEST_HANDLER
#include "database/DatabaseHandler.hpp"
#include "messages.pb.h"
namespace LmsAPI {
namespace Server {
class AudioCollectionRequestHandler
{
public:
AudioCollectionRequestHandler(Database::Handler& db);
bool process(const AudioCollectionRequest& request, AudioCollectionResponse& response);
private:
bool processGetRevision(AudioCollectionResponse::Revision& response);
bool processGetArtists(const AudioCollectionRequest::GetArtistList& request, AudioCollectionResponse::ArtistList& response);
bool processGetGenres(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response);
bool processGetReleases(const AudioCollectionRequest::GetReleaseList& request, AudioCollectionResponse::ReleaseList& response);
bool processGetTracks(const AudioCollectionRequest::GetTrackList& request, AudioCollectionResponse::TrackList& response);
bool processGetCoverArt(const AudioCollectionRequest::GetCoverArt& request, AudioCollectionResponse& response);
Database::Handler& _db;
static const std::size_t _maxListArtists = 256;
static const std::size_t _maxListGenres = 256;
static const std::size_t _maxListReleases = 128;
static const std::size_t _maxListTracks = 128;
static const std::size_t _minCoverArtSize = 64; // in pixels, square
static const std::size_t _maxCoverArtSize = 512; // in pixels, square
};
} // namespace LmsAPI
} // namespace Server
#endif
-105
View File
@@ -1,105 +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 <http://www.gnu.org/licenses/>.
*/
#include <Wt/Auth/Identity>
#include "logger/Logger.hpp"
#include "AuthRequestHandler.hpp"
namespace LmsAPI {
namespace Server {
AuthRequestHandler::AuthRequestHandler(Database::Handler& db)
: _db(db)
{
}
bool
AuthRequestHandler::process(const AuthRequest& request, AuthResponse& response)
{
bool res = false;
switch (request.type())
{
case AuthRequest::TypePassword:
if (request.has_password())
{
res = processPassword(request.password(), *response.mutable_password_result());
if (res)
response.set_type(AuthResponse::TypePasswordResult);
}
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AuthRequest::TypePassword";
break;
}
return res;
}
bool
AuthRequestHandler::processPassword(const AuthRequest::Password& request, AuthResponse::PasswordResult& response)
{
bool res = false;
// Get the user
const Wt::Auth::User& user = _db.getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, request.user_login());
if (user.isValid())
{
// Now attempt to log the user in
Wt::Auth::PasswordResult result = _db.getPasswordService().verifyPassword(user, request.user_password());
switch( result )
{
case Wt::Auth::PasswordInvalid:
response.set_type(AuthResponse::PasswordResult::TypePasswordInvalid);
LMS_LOG(MOD_REMOTE, SEV_NOTICE) << "User '" << request.user_login() << "': invalid password";
res = true;
break;
case Wt::Auth::LoginThrottling:
response.set_type(AuthResponse::PasswordResult::TypeLoginThrottling);
response.set_delay(_db.getPasswordService().delayForNextAttempt(user));
res = true;
break;
case Wt::Auth::PasswordValid:
response.set_type(AuthResponse::PasswordResult::TypePasswordValid);
// Log the user in
_db.getLogin().login( user );
LMS_LOG(MOD_REMOTE, SEV_NOTICE) << "User '" << request.user_login() << "' successfully logged in";
res = true;
break;
default:
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Cannot handle password result!";
break;
}
}
else
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Invalid user '" << request.user_login();
response.set_type(AuthResponse::PasswordResult::TypePasswordInvalid);
}
return res;
}
} // namespace Server
} // namespace LmsAPI
-47
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef LMSAPI_AUTH_REQUEST_HANDLER
#define LMSAPI_AUTH_REQUEST_HANDLER
#include "database/DatabaseHandler.hpp"
#include "messages.pb.h"
namespace LmsAPI {
namespace Server {
class AuthRequestHandler
{
public:
AuthRequestHandler(Database::Handler& db);
bool process(const AuthRequest& request, AuthResponse& response);
private:
bool processPassword(const AuthRequest::Password& request, AuthResponse::PasswordResult& response);
Database::Handler& _db;
};
} // namespace Server
} // namespace LmsAPI
#endif
-250
View File
@@ -1,250 +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 <http://www.gnu.org/licenses/>.
*/
#include <utility>
#include <vector>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include "logger/Logger.hpp"
#include "messages.pb.h"
#include "RequestHandler.hpp"
#include "ConnectionManager.hpp"
#include "Connection.hpp"
namespace LmsAPI {
namespace Server {
Connection::Connection(boost::asio::io_service& ioService,
boost::asio::ssl::context& context,
ConnectionManager& manager,
Wt::Dbo::SqlConnectionPool& connectionPool)
: _closing(false),
_socket(ioService, context),
_connectionManager(manager),
_requestHandler(connectionPool)
{
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Server::Connection::Connection, Creating connection";
}
void
Connection::start()
{
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Starting connection...";
_socket.async_handshake(boost::asio::ssl::stream_base::server,
boost::bind(&Connection::handleHandshake, this,
boost::asio::placeholders::error));
}
void
Connection::handleHandshake(const boost::system::error_code& error)
{
if (!error)
{
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Handshake successfully performed... Now reading messages";
readMsg();
}
else if (error != boost::asio::error::operation_aborted)
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Connection::handleHandshake: " << error.message();
_connectionManager.stop(shared_from_this());
}
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Handshake error: " << error.message();
}
void
Connection::readMsg()
{
// Read a header first
boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(LmsAPI::Header::size);
boost::asio::async_read(_socket,
bufs,
boost::asio::transfer_exactly(LmsAPI::Header::size),
boost::bind(&Connection::handleReadHeader, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void
Connection::stop()
{
if (!_closing)
{
boost::system::error_code ec;
_closing = true;
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Server::Connection::stop, Stopping connection " << this;
_socket.shutdown(ec);
if (ec)
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Error while shutting down connection " << this << ": " << ec.message();
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Server::Connection::stop, connection stopped " << this;
}
else
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Stop: close already in progress...";
}
void
Connection::handleReadHeader(const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (!error)
{
if (bytes_transferred != LmsAPI::Header::size)
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "bytes_transferred (" << bytes_transferred << ") != LmsAPI::Header::size!";
_connectionManager.stop(shared_from_this());
return;
}
_inputStreamBuf.commit(bytes_transferred);
std::istream is(&_inputStreamBuf);
LmsAPI::Header header;
if (!header.from_istream(is))
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Cannot read header from buffer!";
_connectionManager.stop(shared_from_this());
return;
}
// Now read the real message payload
boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(header.getDataSize());
boost::asio::async_read(_socket,
bufs,
boost::asio::transfer_exactly(header.getDataSize()),
boost::bind(&Connection::handleReadMsg, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else if (error != boost::asio::error::operation_aborted)
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Connection::handleReadHeader: " << error.message();
_connectionManager.stop(shared_from_this());
}
}
void
Connection::handleReadMsg(const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (!error)
{
_inputStreamBuf.commit(bytes_transferred);
std::istream is(&_inputStreamBuf);
std::ostream os(&_outputStreamBuf);
LmsAPI::ServerMessage response;
LmsAPI::ClientMessage request;
if (!request.ParseFromIstream(&is))
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Parse request failed!";
_connectionManager.stop(shared_from_this());
return;
}
if (!_requestHandler.process(request, response))
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Process request failed!";
_connectionManager.stop(shared_from_this());
return;
}
{
boost::system::error_code ec;
if (!response.SerializeToOstream(&os))
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Cannot serialize to ostream!";
_connectionManager.stop(shared_from_this());
return;
}
if (_outputStreamBuf.size() >= LmsAPI::Header::max_data_size)
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "output message is too big! " << _outputStreamBuf.size() << " > " << LmsAPI::Header::max_data_size;
_connectionManager.stop(shared_from_this());
return;
}
std::array<unsigned char, LmsAPI::Header::size> headerBuffer;
{
LmsAPI::Header header;
header.setDataSize(_outputStreamBuf.size());
header.to_buffer(headerBuffer);
}
std::size_t n = boost::asio::write(_socket,
boost::asio::buffer(headerBuffer),
boost::asio::transfer_exactly(LmsAPI::Header::size),
ec);
if (ec)
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "cannot write header: " << error.message();
_connectionManager.stop(shared_from_this());
}
else
{
assert(n == LmsAPI::Header::size);
}
// Now send serialized payload
n = boost::asio::write(_socket,
_outputStreamBuf.data(),
boost::asio::transfer_exactly(_outputStreamBuf.size()),
ec);
if (ec)
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "cannot write msg: " << error.message();
_connectionManager.stop(shared_from_this());
}
else
{
assert(n == _outputStreamBuf.size());
_outputStreamBuf.consume(n);
}
}
// All good here, read another message
readMsg();
}
else if (error != boost::asio::error::operation_aborted)
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Connection::handleRead: " << error.message();
_connectionManager.stop(shared_from_this());
}
}
} // namespace Server
} // namespace LmsAPI
-98
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef LMSAPI_CONNECTION_HPP
#define LMSAPI_CONNECTION_HPP
#include <array>
#include <memory>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include "RequestHandler.hpp"
#include "messages/Header.hpp"
namespace LmsAPI {
namespace Server {
class ConnectionManager;
/// Represents a single connection from a client.
class Connection : public std::enable_shared_from_this<Connection>
{
public:
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
Connection(const Connection&) = delete;
Connection& operator=(const Connection&) = delete;
typedef std::shared_ptr<Connection> pointer;
/// Construct a connection with the given io_service.
explicit Connection(boost::asio::io_service& ioService, boost::asio::ssl::context& context,
ConnectionManager& manager,
Wt::Dbo::SqlConnectionPool& connectionPool);
ssl_socket::lowest_layer_type& getSocket() {return _socket.lowest_layer();}
/// Start the first asynchronous operation for the connection.
void start();
/// Stop all asynchronous operations associated with the connection.
void stop();
private:
bool _closing;
/// Read a new message on the the connection
void readMsg();
/// Handle completion of ssl handshake
void handleHandshake(const boost::system::error_code& error);
/// Handle completion of a read operation.
void handleReadHeader(const boost::system::error_code& e,
std::size_t bytes_transferred);
void handleReadMsg(const boost::system::error_code& e,
std::size_t bytes_transferred);
/// Socket for the connection.
ssl_socket _socket;
/// The manager for this connection.
ConnectionManager& _connectionManager;
/// The handler used to process the incoming requests.
RequestHandler _requestHandler;
boost::asio::streambuf _inputStreamBuf;
boost::asio::streambuf _outputStreamBuf;
};
} // namespace Server
} // namespace LmsAPI
#endif
-62
View File
@@ -1,62 +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 <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <boost/foreach.hpp>
#include "ConnectionManager.hpp"
namespace LmsAPI {
namespace Server {
ConnectionManager::ConnectionManager()
{
}
void
ConnectionManager::start(Connection::pointer c)
{
_connections.insert(c);
c->start();
}
void
ConnectionManager::stop(Connection::pointer c)
{
_connections.erase(c);
c->stop();
}
void
ConnectionManager::stopAll()
{
BOOST_FOREACH(Connection::pointer c, _connections)
{
c->stop();
}
_connections.clear();
}
} // namespace Server
} // namespace LmsAPI
-58
View File
@@ -1,58 +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 <http://www.gnu.org/licenses/>.
*/
#ifndef REMOTE_CONNECTION_MANAGER_HPP
#define REMOTE_CONNECTION_MANAGER_HPP
#include <set>
#include "Connection.hpp"
namespace LmsAPI {
namespace Server {
/// Manages open connections so that they may be cleanly stopped when the server
/// needs to shut down.
class ConnectionManager
{
public:
ConnectionManager(const ConnectionManager&) = delete;
ConnectionManager& operator=(const ConnectionManager&) = delete;
ConnectionManager();
/// Add the specified connection to the manager and start it.
void start(Connection::pointer c);
/// Stop the specified connection.
void stop(Connection::pointer c);
/// Stop all connections.
void stopAll();
private:
/// The managed connections.
std::set<Connection::pointer> _connections;
};
} // namespace Server
} // namespace LmsAPI
#endif
-217
View File
@@ -1,217 +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 <http://www.gnu.org/licenses/>.
*/
#include "logger/Logger.hpp"
#include "MediaRequestHandler.hpp"
#include "database/Types.hpp"
namespace LmsAPI {
namespace Server {
MediaRequestHandler::MediaRequestHandler(Database::Handler& db)
: _db(db)
{}
bool
MediaRequestHandler::process(const MediaRequest& request, MediaResponse& response)
{
bool res = false;
switch (request.type())
{
case MediaRequest::TypeMediaPrepare:
if (request.has_prepare())
{
if (request.prepare().has_audio())
res = processAudioPrepare(request.prepare().audio(), *response.mutable_prepare_result());
else if (request.prepare().has_video())
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Video prepare not supported!";
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!";
if (res)
response.set_type(MediaResponse::TypePrepareResult);
}
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!";
break;
case MediaRequest::TypeMediaGetPart:
if (request.has_get_part())
{
res = processGetPart(request.get_part(), *response.mutable_part_result());
if (res)
response.set_type(MediaResponse::TypePartResult);
}
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaGet!";
break;
case MediaRequest::TypeMediaTerminate:
if (request.has_terminate())
{
res = processTerminate(request.terminate(), *response.mutable_terminate_result());
if (res)
response.set_type(MediaResponse::TypePartResult);
}
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaTerminate!";
break;
default:
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled MediaRequest type = " << request.type();
}
return res;
}
bool
MediaRequestHandler::processAudioPrepare(const MediaRequest::Prepare::Audio& request, MediaResponse::PrepareResult& response)
{
std::size_t bitrate;
Transcode::Format::Encoding format;
switch( request.codec_type())
{
case MediaRequest::Prepare::AudioCodecTypeOGA: format = Transcode::Format::OGA; break;
default:
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled codec type = " << request.codec_type();
return false;
}
switch( request.bitrate() )
{
case MediaRequest::Prepare::AudioBitrate_32_kbps: bitrate = 32000; break;
case MediaRequest::Prepare::AudioBitrate_64_kbps: bitrate = 64000; break;
case MediaRequest::Prepare::AudioBitrate_96_kbps: bitrate = 96000; break;
case MediaRequest::Prepare::AudioBitrate_128_kbps: bitrate = 128000; break;
case MediaRequest::Prepare::AudioBitrate_192_kbps: bitrate = 192000; break;
case MediaRequest::Prepare::AudioBitrate_256_kbps: bitrate = 256000; break;
default:
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled bitrate type = " << request.bitrate();
return false;
}
// TODO use user's bitrate limits!
// TODO limit transcoder number by user?
if (_transcoders.size() + 1 > _maxTranscoders)
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Transcoder limit reached!" << std::endl;
// Just answer an empty response, dont delete existing trasncode jobs
return true;
}
try
{
Wt::Dbo::Transaction transaction( _db.getSession());
Database::Track::pointer track = Database::Track::getById( _db.getSession(), request.track_id() );
if (!track)
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Requested track does not exist" << std::endl;
// Track does no longer exist, just answer an empty response
return true;
}
Transcode::InputMediaFile inputFile(track->getPath());
Transcode::Parameters parameters(inputFile, Transcode::Format::get( format ));
parameters.setBitrate( Transcode::Stream::Audio, bitrate);
std::shared_ptr<Transcode::AvConvTranscoder> transcoder = std::make_shared<Transcode::AvConvTranscoder>( parameters );
// now get a unique id (relative to this connection!)
uint32_t handle = _curHandle++;
assert(_transcoders.find(handle) == _transcoders.end());
_transcoders[handle] = transcoder;
response.set_handle(handle);
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Set up new transcode, handle = " << handle;
}
catch(std::exception& e)
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Caught exception: " << e.what();
return false;
}
return true;
}
bool
MediaRequestHandler::processGetPart(const MediaRequest::GetPart& request, MediaResponse::PartResult& response)
{
std::size_t dataSize = request.requested_data_size();
std::vector<unsigned char> data;
if (dataSize > _maxPartSize)
dataSize = _maxPartSize;
TranscoderMap::iterator itTranscoder = _transcoders.find(request.handle());
if (itTranscoder == _transcoders.end())
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No transcoder found for handle " << request.handle();
return true;
}
std::shared_ptr<Transcode::AvConvTranscoder> transcoder = itTranscoder->second;
if (!transcoder->isComplete())
{
data.reserve(dataSize);
transcoder->process(data, dataSize);
}
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler::processGetPart, handle = " << request.handle() << ", isComplete = " << std::boolalpha << transcoder->isComplete() << ", size = " << data.size();
std::copy(data.begin(), data.end(), std::back_inserter(*response.mutable_data()));
return true;
}
bool
MediaRequestHandler::processTerminate(const MediaRequest::Terminate& request, MediaResponse::TerminateResult& response)
{
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler: resetting transcoder for handle " << request.handle();
if (_transcoders.find(request.handle()) == _transcoders.end())
{
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No transcoder found for handle " << request.handle();
return true;
}
else
{
_transcoders.erase(request.handle());
}
return true;
}
} // namespace LmsAPI
} // namespace Server
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef REMOTE_MEDIA_REQUEST_HANDLER
#define REMOTE_MEDIA_REQUEST_HANDLER
#include <map>
#include <memory>
#include "database/DatabaseHandler.hpp"
#include "transcode/AvConvTranscoder.hpp"
#include "media.pb.h"
namespace LmsAPI {
namespace Server {
class MediaRequestHandler
{
public:
MediaRequestHandler(Database::Handler& db);
bool process(const MediaRequest& request, MediaResponse& response);
private:
bool processAudioPrepare(const MediaRequest::Prepare::Audio& request, MediaResponse::PrepareResult& response);
bool processGetPart(const MediaRequest::GetPart& request, MediaResponse::PartResult& response);
bool processTerminate(const MediaRequest::Terminate& request, MediaResponse::TerminateResult& response);
// bool processVideoPrepare(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response);
typedef std::map<uint32_t, std::shared_ptr<Transcode::AvConvTranscoder> > TranscoderMap;
TranscoderMap _transcoders;
Database::Handler& _db;
uint32_t _curHandle = 0;
static const std::size_t _maxPartSize = 65536 - 128;
static const std::size_t _maxTranscoders = 1;
};
} // namespace Server
} // namespace LmsAPI
#endif
-101
View File
@@ -1,101 +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 <http://www.gnu.org/licenses/>.
*/
#include "logger/Logger.hpp"
#include "RequestHandler.hpp"
namespace LmsAPI {
namespace Server {
RequestHandler::RequestHandler(Wt::Dbo::SqlConnectionPool &connectionPool)
: _db( connectionPool ),
_authRequestHandler(_db),
_audioCollectionRequestHandler(_db),
_mediaRequestHandler(_db)
{
}
RequestHandler::~RequestHandler()
{
// TODO manually log out user if needed?
_db.getLogin().logout();
}
bool
RequestHandler::process(const ClientMessage& request, ServerMessage& response)
{
bool res = false;
switch(request.type())
{
case ClientMessage::AuthRequest:
if (request.has_auth_request())
{
res = _authRequestHandler.process(request.auth_request(), *response.mutable_auth_response());
if (res)
response.set_type(ServerMessage::AuthResponse);
}
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad ClientMessage::AuthRequest !";
break;
case ClientMessage::AudioCollectionRequest:
// Not allowed if the user is not logged in
if (_db.getLogin().loggedIn())
{
if (request.has_audio_collection_request())
{
res = _audioCollectionRequestHandler.process(request.audio_collection_request(), *response.mutable_audio_collection_response());
if (res)
response.set_type( ServerMessage::AudioCollectionResponse);
}
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad ClientMessage::AudioCollectionRequest message!";
}
break;
case ClientMessage::MediaRequest:
// Not allowed if the user is not logged in
if (_db.getLogin().loggedIn())
{
if (request.has_media_request())
{
res = _mediaRequestHandler.process(request.media_request(), *response.mutable_media_response());
if (res)
response.set_type( ServerMessage::MediaResponse);
}
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Malformed ClientMessage::MediaRequest message!";
}
break;
default:
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled message type = " << request.type();
}
return res;
}
} // namespace Server
} // namespace LmsAPI
-58
View File
@@ -1,58 +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 <http://www.gnu.org/licenses/>.
*/
#ifndef REMOTE_REQUEST_HANDLER
#define REMOTE_REQUEST_HANDLER
#include <Wt/Dbo/SqlConnectionPool>
#include "messages.pb.h"
#include "database/DatabaseHandler.hpp"
#include "AuthRequestHandler.hpp"
#include "AudioCollectionRequestHandler.hpp"
#include "MediaRequestHandler.hpp"
namespace LmsAPI {
namespace Server {
class RequestHandler
{
public:
RequestHandler(Wt::Dbo::SqlConnectionPool& connectionPool);
~RequestHandler();
bool process(const ClientMessage& request, ServerMessage& response);
private:
Database::Handler _db;
AuthRequestHandler _authRequestHandler;
AudioCollectionRequestHandler _audioCollectionRequestHandler;
MediaRequestHandler _mediaRequestHandler;
};
} // namespace Server
} // namespace LmsAPI
#endif
-112
View File
@@ -1,112 +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 <http://www.gnu.org/licenses/>.
*/
#include <utility>
#include <boost/asio/placeholders.hpp>
#include <boost/bind.hpp>
#include "logger/Logger.hpp"
#include "Server.hpp"
namespace LmsAPI {
namespace Server {
Server::Server(const endpoint_type& bindEndpoint,
boost::filesystem::path certPath,
boost::filesystem::path privKeyPath,
boost::filesystem::path dhPath,
Wt::Dbo::SqlConnectionPool& connectionPool)
:
_acceptor(_ioService, bindEndpoint, true /*SO_REUSEADDR*/),
_connectionManager(),
_context(boost::asio::ssl::context::tlsv1_server),
_connectionPool(connectionPool)
{
_ioService.setThreadCount(1); // TODO parametrize
_context.set_options( boost::asio::ssl::context::default_workarounds // TODO check this thing
| boost::asio::ssl::context::single_dh_use
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::no_sslv3
);
// context_.set_password_callback(boost::bind(&server::get_password, this));
_context.use_certificate_chain_file(certPath.string());
_context.use_private_key_file(privKeyPath.string(), boost::asio::ssl::context::pem);
_context.use_tmp_dh_file(dhPath.string());
}
void
Server::start()
{
// While the server is running, there is always at least one
// asynchronous operation outstanding: the asynchronous accept call waiting
// for new incoming connections.
asyncAccept();
_ioService.start();
}
void
Server::asyncAccept()
{
std::shared_ptr<Connection> newConnection = std::make_shared<Connection>(_ioService, _context, _connectionManager, _connectionPool);
_acceptor.async_accept(newConnection->getSocket(),
boost::bind(&Server::handleAccept, this, newConnection, boost::asio::placeholders::error));
}
void
Server::handleAccept(std::shared_ptr<Connection> newConnection, boost::system::error_code ec)
{
// Check whether the server was stopped before this
// completion handler had a chance to run.
if (!_acceptor.is_open())
{
return;
}
if (!ec)
{
_connectionManager.start(newConnection);
// Accept another connection
// TODO: add some limit?
asyncAccept();
}
else
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "handleAccept: " << ec.message();
}
void
Server::stop()
{
// The server is stopped by cancelling all outstanding asynchronous
// operations.
_acceptor.close();
_connectionManager.stopAll();
_ioService.stop();
}
} // namespace Server
} // namespace LmsAPI
-82
View File
@@ -1,82 +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 <http://www.gnu.org/licenses/>.
*/
#ifndef REMOTE_SERVER_HPP
#define REMOTE_SERVER_HPP
#include <Wt/WIOService>
#include <Wt/Dbo/SqlConnectionPool>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/filesystem.hpp>
#include "Connection.hpp"
#include "ConnectionManager.hpp"
#include "RequestHandler.hpp"
namespace LmsAPI {
namespace Server {
class Server
{
public:
Server(const Server&) = delete;
Server& operator=(const Server&) = delete;
typedef boost::asio::ip::tcp::endpoint endpoint_type;
// Serve up data from the given database
Server(const endpoint_type& bindEndpoint,
boost::filesystem::path certPath,
boost::filesystem::path privKeyPath,
boost::filesystem::path dhPath,
Wt::Dbo::SqlConnectionPool& connectionPool);
// Run the server's io_service loop.
void start();
void stop();
private:
/// Perform an asynchronous accept operation.
void asyncAccept();
void handleAccept(std::shared_ptr<Connection> newConnection, boost::system::error_code ec);
Wt::WIOService _ioService;
/// Acceptor used to listen for incoming connections.
boost::asio::ip::tcp::acceptor _acceptor;
/// The connection manager which owns all live connections.
ConnectionManager _connectionManager;
boost::asio::ssl::context _context;
/// The database to be used for requests
Wt::Dbo::SqlConnectionPool& _connectionPool;
};
} // namespace Server
} // namespace LmsAPI
#endif
+26 -95
View File
@@ -17,105 +17,36 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/keywords/filter.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/attributes/constant.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/log/attributes/named_scope.hpp>
#include <boost/date_time/posix_time/ptime.hpp>
#include "config/ConfigReader.hpp"
#include "Logger.hpp" #include "Logger.hpp"
Logger& std::string getModuleName(Module mod)
Logger::instance()
{ {
static Logger instance; switch (mod)
return instance;
}
Logger::Logger()
{
// Initialiaz loggers
static const std::vector<Module> modules =
{ {
MOD_AV, case Module::AV: return "AV";
MOD_COVER, case Module::COVER: return "COVER";
MOD_DB, case Module::DB: return "DB";
MOD_DBUPDATER, case Module::DBUPDATER: return "DB UPDATER";
MOD_MAIN, case Module::MAIN: return "MAIN";
MOD_METADATA, case Module::METADATA: return "METADATA";
MOD_REMOTE, case Module::REMOTE: return "REMOTE";
MOD_SERVICE, case Module::SERVICE: return "SERVICE";
MOD_TRANSCODE, case Module::TRANSCODE: return "TRANSCODE";
MOD_UI, case Module::UI: return "UI";
};
for(Module module : modules)
_loggers[module].add_attribute("Module", boost::log::attributes::constant< Module >(module));
}
boost::log::sources::severity_logger< Severity >&
Logger::get(Module module)
{
return _loggers[module];
}
void
Logger::init()
{
boost::log::add_common_attributes();
boost::log::register_simple_formatter_factory< Severity, char >("Severity");
if (ConfigReader::instance().getBool("main.logger.file.enable", false))
{
boost::log::add_file_log
(
boost::log::keywords::file_name = ConfigReader::instance().getString("main.logger.file.path") + std::string(".%N"),
boost::log::keywords::rotation_size = 10 * 1024 * 1024,
boost::log::keywords::open_mode = std::ios_base::app,
boost::log::keywords::auto_flush = true,
boost::log::keywords::format = (
boost::log::expressions::stream
<< boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "[%Y-%m-%d %H:%M:%S]")
<< " [" << boost::log::expressions::attr< Module >("Module") << "]"
<< " [" << boost::log::expressions::attr< Severity >("Severity") << "]"
<< " " << boost::log::expressions::smessage
)
);
} }
return "";
if (ConfigReader::instance().getBool("main.logger.console.enable", false)) }
{
boost::log::add_console_log(std::cout, std::string getSeverityName(Severity sev)
boost::log::keywords::format = ( {
boost::log::expressions::stream switch (sev)
<< boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "[%Y-%m-%d %H:%M:%S]") {
<< " [" << boost::log::expressions::attr< Module >("Module") << "]" case Severity::FATAL: return "fatal";
<< " [" << boost::log::expressions::attr< Severity >("Severity") << "]" case Severity::ERROR: return "error";
<< " " << boost::log::expressions::smessage case Severity::WARNING: return "warning";
) case Severity::INFO: return "info";
); case Severity::DEBUG: return "debug";
} }
return "";
boost::log::core::get()->set_filter
(
boost::log::expressions::attr<Severity>("Severity") <= ConfigReader::instance().getULong("main.logger.level", SEV_DEBUG)
);
} }
+25 -98
View File
@@ -20,112 +20,39 @@
#ifndef LOGGER_HPP__ #ifndef LOGGER_HPP__
#define LOGGER_HPP__ #define LOGGER_HPP__
#include <map> #include <Wt/WServer>
#include <Wt/WApplication>
#include <Wt/WLogger>
#include <boost/log/expressions/keyword_fwd.hpp> #include <string>
#include <boost/log/expressions/keyword.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/attributes/named_scope.hpp>
#define LMS_LOG(module, level) BOOST_LOG_SEV(Logger::instance().get(module), level) enum class Severity
enum Severity
{ {
SEV_CRIT = 2, FATAL,
SEV_ERROR = 3, ERROR,
SEV_WARNING = 4, WARNING,
SEV_NOTICE = 5, INFO,
SEV_INFO = 6, DEBUG,
SEV_DEBUG = 7,
}; };
enum Module enum class Module
{ {
MOD_AV = 0, AV,
MOD_COVER, COVER,
MOD_DB, DB,
MOD_DBUPDATER, DBUPDATER,
MOD_MAIN, MAIN,
MOD_METADATA, METADATA,
MOD_REMOTE, REMOTE,
MOD_SERVICE, SERVICE,
MOD_TRANSCODE, TRANSCODE,
MOD_UI, UI,
}; };
BOOST_LOG_ATTRIBUTE_KEYWORD(module, "Module", Module) std::string getModuleName(Module mod);
std::string getSeverityName(Severity sev);
class Logger #define LMS_LOG(module, level) Wt::log(getSeverityName(Severity::level)) << Wt::WLogger::sep << "[" << getModuleName(Module::module) << "]" << Wt::WLogger::sep
{
public:
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
static Logger& instance();
void init();
boost::log::sources::severity_logger< Severity >&
get(Module module);
private:
Logger();
std::map<Module, boost::log::sources::severity_logger< Severity > > _loggers;
};
// The formatting logic for the severity level
template< typename CharT, typename TraitsT >
inline std::basic_ostream< CharT, TraitsT >& operator<< (
std::basic_ostream< CharT, TraitsT >& strm, Severity lvl)
{
static const char* const str[] =
{
"",
"",
"CRIT",
"ERROR",
"WARNING",
"NOTICE",
"INFO",
"DEBUG"
};
if (static_cast< std::size_t >(lvl) < (sizeof(str) / sizeof(*str)))
strm << str[lvl];
else
strm << static_cast< int >(lvl);
return strm;
}
template< typename CharT, typename TraitsT >
inline std::basic_ostream< CharT, TraitsT >& operator<< (
std::basic_ostream< CharT, TraitsT >& strm, Module val)
{
const char* res = NULL;
switch(val)
{
case MOD_AV: res = "AV"; break;
case MOD_COVER: res = "COVER"; break;
case MOD_DB: res = "DB"; break;
case MOD_DBUPDATER: res = "DBUPDATER"; break;
case MOD_MAIN: res = "MAIN"; break;
case MOD_METADATA: res = "METADATA"; break;
case MOD_REMOTE: res = "REMOTE"; break;
case MOD_SERVICE: res = "SERVICE"; break;
case MOD_TRANSCODE: res = "TRANSCODE"; break;
case MOD_UI: res = "UI"; break;
}
if (res)
strm << res;
else
strm << static_cast< int >(val);
return strm;
}
#endif // LOGGER_HPP__
#endif
+38 -41
View File
@@ -20,22 +20,23 @@
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include "config/config.h" #include "config/config.h"
#include "config/ConfigReader.hpp" #include "av/AvInfo.hpp"
#include "transcode/AvConvTranscoder.hpp" #include "av/AvTranscoder.hpp"
#include "av/Common.hpp"
#include "logger/Logger.hpp" #include "logger/Logger.hpp"
#include "cover/CoverArtGrabber.hpp" #include "image/Image.hpp"
#include "ui/LmsApplication.hpp"
#include "service/ServiceManager.hpp" #include "service/ServiceManager.hpp"
#include "service/DatabaseUpdateService.hpp" #include "service/DatabaseUpdateService.hpp"
#include "service/UserInterfaceService.hpp"
#if defined HAVE_LMSAPI #if defined HAVE_LMSAPI
#include "service/LmsAPIServerService.hpp" #include "service/LmsAPIServerService.hpp"
#endif #endif
#include <Wt/WServer>
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
int res = EXIT_FAILURE; int res = EXIT_FAILURE;
assert(argc > 0); assert(argc > 0);
@@ -43,65 +44,61 @@ int main(int argc, char* argv[])
try try
{ {
// TODO generate a nice command line help with args
// Open configuration file Wt::WServer server(argv[0]);
boost::filesystem::path configFile("/etc/lms.conf"); // TODO server.setServerConfiguration (argc, argv);
if (argc > 1)
configFile = boost::filesystem::path(argv[1]);
if ( !boost::filesystem::exists(configFile)) Wt::WServer::instance()->logger().configure("*"); // log everything
{
std::cerr << "Config file '" << configFile << "' does not exist!" << std::endl;
return EXIT_FAILURE;
}
else if (!boost::filesystem::is_regular(configFile))
{
std::cerr << "Config file '" << configFile << "' is not regular!" << std::endl;
return EXIT_FAILURE;
}
ConfigReader::instance().setFile(configFile);
Logger::instance().init();
CoverArt::Grabber::instance().init();
Service::ServiceManager& serviceManager = Service::ServiceManager::instance(); Service::ServiceManager& serviceManager = Service::ServiceManager::instance();
// lib init // lib init
Image::init(argv[0]);
Av::AvInit(); Av::AvInit();
Transcode::AvConvTranscoder::init(); Av::Transcoder::init();
Database::Handler::configureAuth(); Database::Handler::configureAuth();
// Initializing a connection pool to the database that will be shared along services // Initializing a connection pool to the database that will be shared along services
std::unique_ptr<Wt::Dbo::SqlConnectionPool> connectionPool( Database::Handler::createConnectionPool( ConfigReader::instance().getString("main.database.path") )); std::unique_ptr<Wt::Dbo::SqlConnectionPool> connectionPool( Database::Handler::createConnectionPool("/var/lms/lms.db")); // TODO use $datadir from autotools
LMS_LOG(MOD_MAIN, SEV_INFO) << "Starting services..."; serviceManager.add( std::make_shared<Service::DatabaseUpdateService>(*connectionPool));
serviceManager.startService( std::make_shared<Service::DatabaseUpdateService>(*connectionPool));
serviceManager.startService( std::make_shared<Service::UserInterfaceService>(boost::filesystem::path(argv[0]), *connectionPool));
#if defined HAVE_LMSAPI #if defined HAVE_LMSAPI
serviceManager.startService( std::make_shared<Service::LmsAPIService>(*connectionPool)); serviceManager.add( std::make_shared<Service::LmsAPIService>(*connectionPool));
#endif #endif
LMS_LOG(MOD_MAIN, SEV_NOTICE) << "Now running..."; // bind entry point
server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create, _1, boost::ref(*connectionPool)));
serviceManager.run(); // Starting the main server
LMS_LOG(MAIN, INFO) << "Starting server...";
server.start();
// Start underlying services
LMS_LOG(MAIN, INFO) << "Starting services...";
serviceManager.start();
LMS_LOG(MAIN, INFO) << "Now running...";
// Waiting for shutdown command
Wt::WServer::waitForShutdown(argv[0]);
LMS_LOG(MAIN, INFO) << "Stopping services...";
serviceManager.stop();
serviceManager.clear();
LMS_LOG(MAIN, INFO) << "Stopping server...";
server.stop();
res = EXIT_SUCCESS; res = EXIT_SUCCESS;
} }
// TODO catch setting not found exception
catch( libconfig::ParseException& e)
{
std::cerr << "Caught libconfig::ParseException! error='" << e.getError() << "', file = '" << e.getFile() << "', line = " << e.getLine() << std::endl;
}
catch( Wt::WServer::Exception& e) catch( Wt::WServer::Exception& e)
{ {
LMS_LOG(MOD_MAIN, SEV_CRIT) << "Caught a WServer::Exception: " << e.what(); LMS_LOG(MAIN, FATAL) << "Caught a WServer::Exception: " << e.what();
} }
catch( std::exception& e) catch( std::exception& e)
{ {
LMS_LOG(MOD_MAIN, SEV_CRIT) << "Caught std::exception: " << e.what(); LMS_LOG(MAIN, FATAL) << "Caught std::exception: " << e.what();
} }
return res; return res;
+46 -57
View File
@@ -23,7 +23,7 @@
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/date_time/posix_time/posix_time.hpp> #include <boost/date_time/posix_time/posix_time.hpp>
#include "av/InputFormatContext.hpp" #include "av/AvInfo.hpp"
#include "logger/Logger.hpp" #include "logger/Logger.hpp"
@@ -36,86 +36,75 @@ bool
AvFormat::parse(const boost::filesystem::path& p, Items& items) AvFormat::parse(const boost::filesystem::path& p, Items& items)
{ {
Av::InputFormatContext input(p); Av::MediaFile mediaFile(p);
if (!input.findStreamInfo()) if (!mediaFile.open())
return false; return false;
std::map<std::string, std::string> metadata; if (!mediaFile.scan())
input.getMetadata().get(metadata); return false;
// HACK or OGG files
// If we did not find tags, searched metadata in streams
if (metadata.empty())
{
// Get input streams
std::vector<Av::Stream> streams = input.getStreams();
BOOST_FOREACH(Av::Stream& stream, streams)
{
stream.getMetadata().get(metadata);
if (!metadata.empty())
break;
}
}
std::map<std::string, std::string> metadata = mediaFile.getMetaData();
// Stream info // Stream info
{ {
std::vector<Av::Stream> avStreams = input.getStreams(); std::vector<AudioStream> audioStreams;
std::vector<AudioStream> audioStreams; std::vector<Av::Stream> streams = mediaFile.getStreams(Av::Stream::Type::Audio);
std::vector<VideoStream> videoStreams;
std::vector<SubtitleStream> subtitleStreams;
BOOST_FOREACH(Av::Stream& avStream, avStreams) for (Av::Stream& stream : streams)
{ {
switch(avStream.getCodecContext().getType()) AudioStream audioStream;
{ audioStream.desc = stream.desc;
case AVMEDIA_TYPE_VIDEO: audioStream.bitRate = stream.bitrate;
if (!avStream.hasAttachedPic())
{
VideoStream stream;
stream.bitRate = avStream.getCodecContext().getBitRate();
videoStreams.push_back(stream);
}
break;
case AVMEDIA_TYPE_AUDIO: audioStreams.push_back(audioStream);
{ }
AudioStream stream;
stream.nbChannels = avStream.getCodecContext().getNbChannels();
stream.bitRate = avStream.getCodecContext().getBitRate();
audioStreams.push_back(stream);
}
break;
case AVMEDIA_TYPE_SUBTITLE: if (!audioStreams.empty())
{ items.insert( std::make_pair(MetaData::Type::AudioStreams, audioStreams));
subtitleStreams.push_back( SubtitleStream() ); }
}
break;
default: {
break; std::vector<VideoStream> videoStreams;
}
std::vector<Av::Stream> streams = mediaFile.getStreams(Av::Stream::Type::Video);
for (Av::Stream& stream : streams)
{
VideoStream videoStream;
videoStream.desc = stream.desc;
videoStream.bitRate = stream.bitrate;
videoStreams.push_back(videoStream);
} }
if (!videoStreams.empty()) if (!videoStreams.empty())
items.insert( std::make_pair(MetaData::Type::VideoStreams, videoStreams)); items.insert( std::make_pair(MetaData::Type::VideoStreams, videoStreams));
if (!audioStreams.empty()) }
items.insert( std::make_pair(MetaData::Type::AudioStreams, audioStreams));
{
std::vector<SubtitleStream> subtitleStreams;
std::vector<Av::Stream> streams = mediaFile.getStreams(Av::Stream::Type::Subtitle);
for (Av::Stream& stream : streams)
{
SubtitleStream subtitleStream;
subtitleStream.desc = stream.desc;
subtitleStreams.push_back(subtitleStream);
}
if (!subtitleStreams.empty()) if (!subtitleStreams.empty())
items.insert( std::make_pair(MetaData::Type::SubtitleStreams, subtitleStreams)); items.insert( std::make_pair(MetaData::Type::SubtitleStreams, subtitleStreams));
} }
// Duration // Duration
items.insert( std::make_pair(MetaData::Type::Duration, boost::posix_time::time_duration( boost::posix_time::seconds( input.getDurationSecs() )) )); items.insert( std::make_pair(MetaData::Type::Duration, mediaFile.getDuration() ));
// Cover // Cover
items.insert( std::make_pair(MetaData::Type::HasCover, input.getNbPictures() > 0)); items.insert( std::make_pair(MetaData::Type::HasCover, mediaFile.hasAttachedPictures()));
// Embedded MetaData // Embedded MetaData
// Make sure to convert strings into UTF-8 // Make sure to convert strings into UTF-8
+9 -5
View File
@@ -48,17 +48,21 @@ namespace MetaData
}; };
// Used by Streams // Used by Streams
struct AudioStream { struct AudioStream
std::size_t nbChannels; {
std::string desc;
std::size_t bitRate; std::size_t bitRate;
}; };
struct VideoStream { struct VideoStream
{
std::string desc;
std::size_t bitRate; std::size_t bitRate;
}; };
struct SubtitleStream { struct SubtitleStream
; {
std::string desc;
}; };
// Type and associated data // Type and associated data
-21
View File
@@ -19,52 +19,31 @@
#include <boost/thread.hpp> #include <boost/thread.hpp>
#include "config/ConfigReader.hpp"
#include "logger/Logger.hpp"
#include "DatabaseUpdateService.hpp" #include "DatabaseUpdateService.hpp"
static std::vector<std::string> splitStrings(const std::string& source)
{
std::vector<std::string> res;
std::istringstream oss(source);
std::string str;
while(oss >> str)
res.push_back(str);
return res;
}
namespace Service { namespace Service {
DatabaseUpdateService::DatabaseUpdateService(Wt::Dbo::SqlConnectionPool &connectionPool) DatabaseUpdateService::DatabaseUpdateService(Wt::Dbo::SqlConnectionPool &connectionPool)
: _metadataParser(), : _metadataParser(),
_databaseUpdater( connectionPool, _metadataParser) _databaseUpdater( connectionPool, _metadataParser)
{ {
_databaseUpdater.setAudioExtensions(splitStrings(ConfigReader::instance().getString("main.database.audio_extensions")));
_databaseUpdater.setVideoExtensions(splitStrings(ConfigReader::instance().getString("main.database.video_extensions")));
} }
void void
DatabaseUpdateService::start(void) DatabaseUpdateService::start(void)
{ {
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, starting...";
_databaseUpdater.start(); _databaseUpdater.start();
} }
void void
DatabaseUpdateService::stop(void) DatabaseUpdateService::stop(void)
{ {
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, stopping...";
_databaseUpdater.stop(); _databaseUpdater.stop();
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, stopped";
} }
void void
DatabaseUpdateService::restart(void) DatabaseUpdateService::restart(void)
{ {
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, restart";
stop(); stop();
start(); start();
} }
+22 -83
View File
@@ -17,11 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "logger/Logger.hpp"
#include <boost/foreach.hpp>
#include <boost/bind.hpp>
#include "ServiceManager.hpp" #include "ServiceManager.hpp"
namespace Service { namespace Service {
@@ -34,112 +29,56 @@ ServiceManager::instance()
} }
ServiceManager::ServiceManager() ServiceManager::ServiceManager()
: _signalSet(_ioService)
{ {
_signalSet.add(SIGINT);
_signalSet.add(SIGTERM);
#if defined(SIGQUIT)
_signalSet.add(SIGQUIT);
#endif // defined(SIGQUIT)
_signalSet.add(SIGHUP);
// Excplicitely ignore SIGCHLD to avoid zombies
// when avconv child processes are being killed
if (::signal(SIGCHLD, SIG_IGN) == SIG_ERR)
throw std::runtime_error("ServiceManager::ServiceManager, signal failed!");
} }
ServiceManager::~ServiceManager() ServiceManager::~ServiceManager()
{ {
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Stopping services..."; stop();
stopServices();
} }
void void
ServiceManager::run() ServiceManager::add(Service::pointer service)
{
asyncWaitSignals();
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "ServiceManager: waiting for events...";
try {
// Wait for events
_ioService.run();
}
catch( std::exception& e )
{
LMS_LOG(MOD_SERVICE, SEV_ERROR) << "ServiceManager: exception in ioService::run: " << e.what();
}
// Stopping services
stopServices();
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "ServiceManager: run complete !";
}
void
ServiceManager::asyncWaitSignals(void)
{
_signalSet.async_wait(boost::bind(&ServiceManager::handleSignal,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::signal_number));
}
void
ServiceManager::startService(Service::pointer service)
{ {
_services.insert(service); _services.insert(service);
service->start(); service->start();
} }
void void
ServiceManager::stopService(Service::pointer service) ServiceManager::del(Service::pointer service)
{ {
_services.erase(service);
service->stop(); service->stop();
_services.erase(service);
} }
void void
ServiceManager::stopServices(void) ServiceManager::clear(void)
{ {
BOOST_FOREACH(Service::pointer service, _services) stop();
_services.clear();
}
void
ServiceManager::start(void)
{
for (Service::pointer service : _services)
service->start();
}
void
ServiceManager::stop(void)
{
for (Service::pointer service : _services)
service->stop(); service->stop();
} }
void void
ServiceManager::restartServices(void) ServiceManager::restart(void)
{ {
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Restarting services..."; for (Service::pointer service : _services)
BOOST_FOREACH(Service::pointer service, _services)
service->restart(); service->restart();
} }
void
ServiceManager::handleSignal(boost::system::error_code /*ec*/, int signo)
{
LMS_LOG(MOD_SERVICE, SEV_INFO) << "Received signal " << signo;
switch (signo)
{
case SIGINT:
case SIGTERM:
case SIGQUIT:
stopServices();
// Do not listen for signals, this will make the ioservice.run return
break;
case SIGHUP:
restartServices();
asyncWaitSignals();
break;
default:
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Unhandled signal " << signo;
}
}
} // namespace Service } // namespace Service
+11 -24
View File
@@ -20,7 +20,6 @@
#ifndef SERVICE_CONTROLER_HPP #ifndef SERVICE_CONTROLER_HPP
#define SERVICE_CONTROLER_HPP #define SERVICE_CONTROLER_HPP
#include <boost/asio.hpp>
#include <set> #include <set>
#include "Service.hpp" #include "Service.hpp"
@@ -35,15 +34,15 @@ class ServiceManager
static ServiceManager& instance(); static ServiceManager& instance();
~ServiceManager(); ~ServiceManager();
void stopService(Service::pointer service); void add(Service::pointer service);
void startService(Service::pointer service); void del(Service::pointer service);
void clear();
void stopAllServices(); void start();
void stop();
void restart();
// Return in case of failure/stop by user template <class T> typename T::pointer get();
void run();
template <class T> typename T::pointer getService();
boost::mutex& mutex() { return _mutex;} boost::mutex& mutex() { return _mutex;}
@@ -53,32 +52,20 @@ class ServiceManager
ServiceManager(ServiceManager const&); // Don't Implement ServiceManager(ServiceManager const&); // Don't Implement
void operator=(ServiceManager const&); // Don't implement void operator=(ServiceManager const&); // Don't implement
void restartServices(void);
void stopServices(void);
void asyncWaitSignals(void);
void handleSignal(boost::system::error_code error, int signo);
boost::mutex _mutex; boost::mutex _mutex;
boost::asio::io_service _ioService;
// Listen for interesting signals
boost::asio::signal_set _signalSet;
std::set<Service::pointer> _services; std::set<Service::pointer> _services;
}; };
template <class T> typename T::pointer template <class T> typename T::pointer
ServiceManager::getService() ServiceManager::get()
{ {
std::set<Service::pointer>::iterator it; std::set<Service::pointer>::iterator it;
for (std::set<Service::pointer>::iterator it = _services.begin(); it != _services.end(); ++it) for (Service::pointer service : _services)
{ {
if (typeid(*(*it)) == typeid(T)) { if (typeid(*(service)) == typeid(T)) {
return std::dynamic_pointer_cast<T>(*it); return std::dynamic_pointer_cast<T>(service);
} }
} }
return std::shared_ptr<T>(); return std::shared_ptr<T>();
-85
View File
@@ -1,85 +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 <http://www.gnu.org/licenses/>.
*/
#include <Wt/Dbo/SqlConnectionPool>
#include "logger/Logger.hpp"
#include "UserInterfaceService.hpp"
#include "ui/LmsApplication.hpp"
#include "config/ConfigReader.hpp"
namespace Service {
UserInterfaceService::UserInterfaceService( boost::filesystem::path runAppPath, Wt::Dbo::SqlConnectionPool& connectionPool)
: _server(runAppPath.string())
{
std::vector<std::string> args;
args.push_back(runAppPath.string());
args.push_back("--docroot=" + ConfigReader::instance().getString("ui.resources.docroot"));
args.push_back("--approot=" + ConfigReader::instance().getString("ui.resources.approot"));
args.push_back("--https-port=" + std::to_string( ConfigReader::instance().getULong("ui.listen-endpoint.port")));
args.push_back("--https-address=" + ConfigReader::instance().getString("ui.listen-endpoint.addr"));
args.push_back("--ssl-certificate=" + ConfigReader::instance().getString("ui.ssl-crypto.cert"));
args.push_back("--ssl-private-key=" + ConfigReader::instance().getString("ui.ssl-crypto.key"));
args.push_back("--ssl-tmp-dh=" + ConfigReader::instance().getString("ui.ssl-crypto.dh"));
// Construct argc/argv
int argc = args.size();
const char* argv[args.size()];
for (int i = 0; i < argc; ++i)
argv[i] = args[i].c_str();
for(int i = 0; i < argc; ++i)
{
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "i = " << i << ", arg = '" << argv[i] << "'";
}
_server.setServerConfiguration (argc, const_cast<char**>(argv));
// bind entry point
_server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create, _1, boost::ref(connectionPool)));
}
void
UserInterfaceService::start(void)
{
_server.start();
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "UserInterfaceService::start -> Service started...";
}
void
UserInterfaceService::stop(void)
{
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "UserInterfaceService::stop -> stopping...";
_server.stop();
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "UserInterfaceService::stop -> stopped!";
}
void
UserInterfaceService::restart(void)
{
}
} // namespace Service
-50
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef WEB_SERVER_SERVICE_HPP
#define WEB_SERVER_SERVICE_HPP
#include <boost/filesystem.hpp>
#include <Wt/WServer>
#include "Service.hpp"
namespace Service {
class UserInterfaceService : public Service
{
public:
UserInterfaceService(boost::filesystem::path runAppPath, Wt::Dbo::SqlConnectionPool& connectionPool);
void start(void);
void stop(void);
void restart(void);
private:
Wt::WServer _server;
};
} //namespace Service
#endif
-82
View File
@@ -1,82 +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 <http://www.gnu.org/licenses/>.
*/
#ifndef AVCONV_TRANSCODER_HPP
#define AVCONV_TRANSCODER_HPP
#include <memory>
#include <iostream>
#include <vector>
#include <boost/iostreams/stream.hpp>
#include <boost/process.hpp>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include "Parameters.hpp"
namespace Transcode
{
class AvConvTranscoder
{
public:
static void init();
~AvConvTranscoder();
AvConvTranscoder(const Parameters& parameters);
const Parameters& getParameters(void) const { return _parameters; }
// Get a bunch of input data
// Place it at the end of the parameter, no more that maxSize bytes
void process(std::vector<unsigned char>& output, std::size_t maxSize);
bool isComplete(void) const { return _isComplete;};
std::size_t getOutputBytes(void) const { return _outputBytes; }
private:
typedef std::shared_ptr<boost::process::child> ChildPtr;
void waitChild();
void killChild();
const Parameters _parameters;
static boost::mutex _mutex;
boost::process::pipe _outputPipe;
boost::iostreams::file_descriptor_source _source;
boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_source> _is;
std::istream _in;
std::shared_ptr<boost::process::child> _child;
static boost::filesystem::path _avConvPath;
bool _isComplete;
std::size_t _outputBytes; // Bytes produced so far
};
} // naspace Transcode
#endif
-84
View File
@@ -1,84 +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 <http://www.gnu.org/licenses/>.
*/
#include <boost/foreach.hpp>
#include <stdexcept>
#include <cassert>
#include "Format.hpp"
namespace Transcode
{
const std::vector<Format> Format::_supportedFormats
{
{Format::OGA, Format::Audio, "audio/ogg", "Ogg"},
{Format::OGV, Format::Video, "video/ogg", "Ogg"},
{Format::MP3, Format::Audio, "audio/mpeg", "MP3"},
{Format::WEBMA, Format::Audio, "audio/webm", "WebM"},
{Format::WEBMV, Format::Video, "video/webm", "WebM"},
{Format::FLA, Format::Audio, "audio/x-flv", "Flash Audio"},
{Format::FLV, Format::Video, "video/x-flv", "Flash Video"},
{Format::M4A, Format::Audio, "audio/mp4", "MP4"},
{Format::M4V, Format::Video, "video/mp4", "MP4"},
};
Format::Format(Encoding encoding, Type type, std::string mimeType, std::string desc)
:
_encoding(encoding),
_type(type),
_mineType(mimeType),
_desc(desc)
{}
const Format&
Format::get(Encoding encoding)
{
BOOST_FOREACH(const Format& format, _supportedFormats)
{
if (format.getEncoding() == encoding)
return format;
}
throw std::runtime_error("Cannot find format");
}
std::vector<Format>
Format::get(Type type)
{
std::vector<Format> res;
BOOST_FOREACH(const Format& format, _supportedFormats)
{
if (format.getType() == type)
res.push_back( format );
}
return res;
}
bool
Format::operator==(const Format& other) const
{
return getEncoding() == other.getEncoding();
}
} // namespace Transcode
-77
View File
@@ -1,77 +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 <http://www.gnu.org/licenses/>.
*/
#ifndef TRANSCODE_FORMAT_HPP
#define TRANSCODE_FORMAT_HPP
#include <vector>
#include <string>
namespace Transcode
{
class Format
{
public:
// Output format
enum Encoding {
OGA,
OGV,
MP3,
WEBMA,
WEBMV,
FLA,
FLV,
M4A,
M4V,
};
enum Type {
Video,
Audio,
};
// Utility
static const Format& get(Encoding encoding);
static std::vector<Format> get(Type type);
Format(Encoding format, Type type, std::string mimeType, std::string desc);
Encoding getEncoding(void) const { return _encoding;}
Type getType(void) const { return _type;}
const std::string& getMimeType(void) const { return _mineType;}
const std::string& getDesc(void) const { return _desc;}
bool operator==(const Format& other) const;
private:
Encoding _encoding;
Type _type;
std::string _mineType;
std::string _desc;
static const std::vector<Format> _supportedFormats;
};
} // namespace Transcode
#endif
-128
View File
@@ -1,128 +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 <http://www.gnu.org/licenses/>.
*/
#include <list>
#include "logger/Logger.hpp"
#include "av/InputFormatContext.hpp"
#include "cover/CoverArtGrabber.hpp"
#include "InputMediaFile.hpp"
namespace Transcode
{
bool getStreamType(enum AVMediaType type, Transcode::Stream::Type& streamType)
{
switch (type) {
case AVMEDIA_TYPE_VIDEO: streamType = Transcode::Stream::Video; return true;
case AVMEDIA_TYPE_AUDIO: streamType = Transcode::Stream::Audio; return true;
case AVMEDIA_TYPE_SUBTITLE: streamType = Transcode::Stream::Subtitle; return true;
default:
return false;
}
}
InputMediaFile::InputMediaFile(const boost::filesystem::path& p)
: _path(p)
{
Av::InputFormatContext input(_path);
if (!input.findStreamInfo())
throw std::runtime_error("Cannot find stream info in file: " + p.string());
// Calculate estimated duration
if (input.getDurationSecs())
_duration = boost::posix_time::seconds(input.getDurationSecs() + 1);
// Get input streams
std::vector<Av::Stream> avStreams = input.getStreams();
std::list<enum AVMediaType> avMediaTypes; // List of encountered streams
for (std::size_t avStreamId = 0; avStreamId < avStreams.size(); ++avStreamId)
{
Av::Stream& avStream(avStreams[avStreamId]);
Stream::Type type;
if (getStreamType(avStream.getCodecContext().getType(), type))
{
// Reject Video stream hat are in fact cover arts
if (avStream.hasAttachedPic())
continue;
avMediaTypes.push_back(avStream.getCodecContext().getType());
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Stream idx " << avStreamId << ", type = " << type << ", bitrate = " << avStream.getCodecContext().getBitRate() << ", codec desc = " << avStream.getCodecContext().getCodecDesc();
_streams.push_back( Stream(avStreamId,
type,
avStream.getCodecContext().getBitRate(),
avStream.getMetadata().get("language"), // TODO define somewhere else?
avStream.getCodecContext().getCodecDesc()
));
}
}
avMediaTypes.unique();
// Scan for best streams
for (enum AVMediaType type : avMediaTypes)
{
Av::Stream::Idx index;
if (input.getBestStreamIdx(type, index))
{
Stream::Type streamType;
if (getStreamType(type, streamType))
_bestStreams.insert(std::make_pair( streamType, index) );
}
else
LMS_LOG(MOD_TRANSCODE, SEV_WARNING) << "Cannot find best stream for type " << type;
}
}
std::vector<Stream>
InputMediaFile::getStreams(Stream::Type type) const
{
std::vector<Stream> res;
for (const Stream& stream : _streams)
{
if (stream.getType() == type)
res.push_back(stream);
}
return res;
}
const Stream&
InputMediaFile::getStream(Stream::Id index) const
{
for (const Stream& stream : _streams)
{
if (stream.getId() == index)
return stream;
}
LMS_LOG(MOD_TRANSCODE, SEV_CRIT) << "Cannot find stream index " << index << " in stream map!";
throw std::runtime_error("InputMediaFile::getStream, cannot find stream idx");
}
} // namespace Transcode
-72
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef TRANSCODE_INPUT_MEDIA_FILE
#define TRANSCODE_INPUT_MEDIA_FILE
#include <map>
#include <string>
#include <boost/filesystem.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "cover/CoverArt.hpp"
#include "Stream.hpp"
namespace Transcode
{
class InputMediaFile
{
public:
enum StreamType
{
StreamAudio,
StreamVideo,
StreamSubtitle,
};
InputMediaFile(const boost::filesystem::path& p);
// Accessors
boost::filesystem::path getPath(void) const {return _path;}
boost::posix_time::time_duration getDuration(void) const {return _duration;}
// Stream handling
const Stream& getStream(Stream::Id id) const;
std::vector<Stream> getStreams(Stream::Type type) const;
const std::map<Stream::Type, Stream::Id>& getBestStreams(void) const {return _bestStreams;}
private:
boost::filesystem::path _path;
boost::posix_time::time_duration _duration;
std::vector<Stream> _streams;
std::map<Stream::Type, Stream::Id> _bestStreams;
};
} // namespace Transcode
#endif // TRANSCODE_INPUT_MEDIA_FILE
-87
View File
@@ -1,87 +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 <http://www.gnu.org/licenses/>.
*/
#include "logger/Logger.hpp"
#include "av/InputFormatContext.hpp"
#include "Parameters.hpp"
namespace Transcode
{
Parameters::Parameters(const InputMediaFile& inputMediaFile,
const Format& outputFormat)
:
_mediaFile(inputMediaFile),
_outputFormat(outputFormat)
{
// By default, select the best stream indexes
_inputStreams = _mediaFile.getBestStreams();
}
std::size_t
Parameters::setBitrate(Stream::Type type, std::size_t bitrate)
{
// Limit the output bitrate to the input bitrate
if (_inputStreams.find(type) != _inputStreams.end())
{
const Transcode::Stream& stream = _mediaFile.getStream( _inputStreams[type] );
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Stream bitrate = " << stream.getBitrate();
if (bitrate > stream.getBitrate())
{
LMS_LOG(MOD_TRANSCODE, SEV_INFO) << "Setting bitrate for stream idx " << _inputStreams[type] << " to input bitrate (" << stream.getBitrate() << ")";
_outputBitrate[type] = stream.getBitrate();
}
else
{
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Setting bitrate for stream idx " << _inputStreams[type] << " to " << bitrate;
_outputBitrate[type] = bitrate;
}
return _outputBitrate[type];
}
else
{
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Cannot find stream type " << type;
return 0;
}
}
std::size_t
Parameters::getOutputBitrate(Stream::Type type) const
{
std::map<Stream::Type, std::size_t>::const_iterator it = _outputBitrate.find(type);
if (it != _outputBitrate.end())
{
return it->second;
}
else
{
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "output bitrate not set for type " << type;
return 0;
}
}
} // namespace Transcode
-77
View File
@@ -1,77 +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 <http://www.gnu.org/licenses/>.
*/
#ifndef TRANSCODE_PARAMETERS_HPP
#define TRANSCODE_PARAMETERS_HPP
#include <string>
#include <boost/filesystem.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "Format.hpp"
#include "InputMediaFile.hpp"
namespace Transcode
{
class Parameters {
public:
Parameters(const InputMediaFile& InputFile, const Format& outputFormat);
// Modifiers
void setOffset(boost::posix_time::time_duration offset) { _offset = offset; } // Set input offset
void setOutputFormat(const Format& format) { _outputFormat = format; }
std::size_t setBitrate(Stream::Type type, std::size_t bitrate);
// Manually select an input stream to output
// There can be only one stream per each type (video, audio, subtitle)
typedef std::map<Stream::Type, Stream::Id> StreamMap;
void selectInputStream(Stream::Type type, Stream::Id id) { _inputStreams[type] = id; }
const StreamMap& getInputStreams(void) const {return _inputStreams;}
//Accessors
boost::posix_time::time_duration getOffset(void) const {return _offset;}
const Format& getOutputFormat(void) const { return _outputFormat; }
std::size_t getOutputBitrate(Stream::Type type) const;
const InputMediaFile& getInputMediaFile(void) const { return _mediaFile;}
InputMediaFile& getInputMediaFile(void) { return _mediaFile;}
private:
InputMediaFile _mediaFile;
boost::posix_time::time_duration _offset; // start input offset
Format _outputFormat; // OGA, OGV, etc.
std::map<Stream::Type, std::size_t> _outputBitrate;
StreamMap _inputStreams;
};
} // namespace Transcode
#endif
-66
View File
@@ -1,66 +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 <http://www.gnu.org/licenses/>.
*/
#ifndef TRANSCODE_STREAM_HPP
#define TRANSCODE_STREAM_HPP
#include <string>
namespace Transcode
{
class Stream
{
public:
enum Type
{
Audio,
Video,
Subtitle,
};
typedef std::size_t Id;
Stream(Id id, Type type, std::size_t bitrate, const std::string& lang, const std::string& desc)
: _id(id), _type(type), _bitrate(bitrate), _language(lang), _desc(desc) {}
// Accessors
Id getId() const { return _id;}
Type getType() const { return _type;}
std::size_t getBitrate() const { return _bitrate;}
const std::string& getLanguage() const { return _language;}
const std::string& getDesc() const { return _desc;}
private:
Id _id;
Type _type;
std::size_t _bitrate;
std::string _language;
std::string _desc;
};
} // namespace Transcode
#endif
+9 -5
View File
@@ -28,6 +28,8 @@
#include <Wt/WVBoxLayout> #include <Wt/WVBoxLayout>
#include <Wt/Auth/Identity> #include <Wt/Auth/Identity>
#include "config/config.h"
#include "logger/Logger.hpp" #include "logger/Logger.hpp"
#include "settings/Settings.hpp" #include "settings/Settings.hpp"
@@ -36,7 +38,9 @@
#include "auth/LmsAuth.hpp" #include "auth/LmsAuth.hpp"
#include "audio/desktop/DesktopAudio.hpp" #include "audio/desktop/DesktopAudio.hpp"
#include "audio/mobile/MobileAudio.hpp" #include "audio/mobile/MobileAudio.hpp"
#if HAVE_VIDEO
#include "video/VideoWidget.hpp" #include "video/VideoWidget.hpp"
#endif
#include "common/LineEdit.hpp" #include "common/LineEdit.hpp"
#include "LmsApplication.hpp" #include "LmsApplication.hpp"
@@ -167,7 +171,7 @@ LmsApplication::handleAuthEvent(void)
{ {
if (DbHandler().getLogin().loggedIn()) if (DbHandler().getLogin().loggedIn())
{ {
LMS_LOG(MOD_UI, SEV_NOTICE) << "User '" << CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) << "' logged in from '" << Wt::WApplication::instance()->environment().clientAddress() << "', user agent = " << Wt::WApplication::instance()->environment().agent() << ", session = " << Wt::WApplication::instance()->sessionId(); LMS_LOG(UI, INFO) << "User '" << CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) << "' logged in from '" << Wt::WApplication::instance()->environment().clientAddress() << "', user agent = " << Wt::WApplication::instance()->environment().agent() << ", session = " << Wt::WApplication::instance()->sessionId();
this->root()->setOverflow(Wt::WContainerWidget::OverflowHidden); this->root()->setOverflow(Wt::WContainerWidget::OverflowHidden);
setConfirmCloseMessage("Closing LMS. Are you sure?"); setConfirmCloseMessage("Closing LMS. Are you sure?");
@@ -196,10 +200,10 @@ LmsApplication::handleAuthEvent(void)
else else
audio = new Desktop::Audio(); audio = new Desktop::Audio();
VideoWidget *videoWidget = new VideoWidget();
leftMenu->addItem("Audio", audio); leftMenu->addItem("Audio", audio);
leftMenu->addItem("Video", videoWidget); #if defined HAVE_VIDEO
leftMenu->addItem("Video", new VideoWidget());
#endif
leftMenu->addItem("Settings", new Settings::Settings()); leftMenu->addItem("Settings", new Settings::Settings());
// Setup a Right-aligned menu. // Setup a Right-aligned menu.
@@ -238,7 +242,7 @@ LmsApplication::handleAuthEvent(void)
} }
else else
{ {
LMS_LOG(MOD_UI, SEV_NOTICE) << "User logged out, session = " << Wt::WApplication::instance()->sessionId(); LMS_LOG(UI, INFO) << "User logged out, session = " << Wt::WApplication::instance()->sessionId();
quit(""); quit("");
redirect("/"); redirect("/");
+70 -66
View File
@@ -64,7 +64,7 @@ AudioMediaPlayer::AudioMediaPlayer(Wt::WContainerWidget *parent)
} }
} }
LMS_LOG(MOD_UI, SEV_INFO) << "Audio player using encoding " << _encoding; LMS_LOG(UI, INFO) << "Audio player using encoding " << _encoding;
// Current Media info // Current Media info
Wt::WHBoxLayout *currentMediaLayout = new Wt::WHBoxLayout(); Wt::WHBoxLayout *currentMediaLayout = new Wt::WHBoxLayout();
@@ -172,85 +172,92 @@ AudioMediaPlayer::AudioMediaPlayer(Wt::WContainerWidget *parent)
} }
void void
AudioMediaPlayer::loadPlayer(void) AudioMediaPlayer::loadPlayer(boost::filesystem::path filePath, Av::TranscodeParameters& parameters)
{ {
_currentFile = filePath;
_currentParameters = parameters;
_mediaPlayer->clearSources(); _mediaPlayer->clearSources();
if (_mediaResource) if (_mediaResource)
delete _mediaResource; delete _mediaResource;
assert( _currentParameters ); _mediaResource = new AvConvTranscodeStreamResource( filePath, parameters, this );
_mediaResource = new AvConvTranscodeStreamResource( *_currentParameters, this );
_mediaPlayer->addSource( getEncoding(), Wt::WLink(_mediaResource)); _mediaPlayer->addSource( getEncoding(), Wt::WLink(_mediaResource));
}
void
AudioMediaPlayer::load(Database::Track::id_type trackId)
{
std::size_t bitrate = 0;
boost::filesystem::path trackPath;
{
Wt::Dbo::Transaction transaction(DboSession());
Database::Track::pointer track = Database::Track::getById(DboSession(), trackId);
bitrate = CurrentUser()->getAudioBitrate();
trackPath = track->getPath();
_mediaTitle->setText ( Wt::WString::fromUTF8(track->getName()) );
_mediaArtistRelease->setText ( Wt::WString::fromUTF8(track->getArtist()->getName()) + " - " + Wt::WString::fromUTF8(track->getRelease()->getName()) );
_mediaCover->setImageLink( Wt::WLink (LmsApplication::instance()->getCoverResource()->getTrackUrl(trackId, 72)));
}
Transcode::Format::Encoding encoding;
switch (_encoding)
{
case Wt::WMediaPlayer::MP3: encoding = Transcode::Format::MP3; break;
case Wt::WMediaPlayer::FLA: encoding = Transcode::Format::FLA; break;
case Wt::WMediaPlayer::OGA: encoding = Transcode::Format::OGA; break;
case Wt::WMediaPlayer::WEBMA: encoding = Transcode::Format::WEBMA; break;
default:
encoding = Transcode::Format::MP3;
}
_timeSlider->setDisabled(false);
try
{
Transcode::Parameters parameters(trackPath, Transcode::Format::get(encoding));
parameters.setBitrate(Transcode::Stream::Audio, bitrate);
_currentParameters = std::make_shared<Transcode::Parameters>( parameters );
}
catch(std::exception &e)
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Cannot load input file '" << trackPath << "'";
return;
}
loadPlayer();
_timeSlider->setRange(0, _currentParameters->getInputMediaFile().getDuration().total_seconds() );
_timeSlider->setValue(0);
_duration->setText( boost::posix_time::to_simple_string( _currentParameters->getInputMediaFile().getDuration() ));
// Auto play // Auto play
_mediaPlayer->play(); _mediaPlayer->play();
} }
void
AudioMediaPlayer::load(Database::Track::id_type trackId)
{
Av::TranscodeParameters parameters;
boost::filesystem::path path;
boost::posix_time::time_duration duration;
{
Wt::Dbo::Transaction transaction(DboSession());
Database::Track::pointer track = Database::Track::getById(DboSession(), trackId);
path = track->getPath();
parameters.setBitrate(Av::Stream::Type::Audio, CurrentUser()->getAudioBitrate() );
duration = track->getDuration();
_mediaTitle->setText ( Wt::WString::fromUTF8(track->getName()) );
_mediaArtistRelease->setText ( Wt::WString::fromUTF8(track->getArtist()->getName()) + " - " + Wt::WString::fromUTF8(track->getRelease()->getName()) );
_mediaCover->setImageLink( Wt::WLink (LmsApplication::instance()->getCoverResource()->getTrackUrl(trackId, 72)));
}
Av::MediaFile mediaFile(path);
if (!mediaFile.open())
{
// No longer exist ? TODO next?
LMS_LOG(UI, INFO) << "Cannot open file '" << path << "'";
return;
}
// It seems to be far better to manually map the streams
// otherwise, some files may have to be fully transcoded to be played by browser...
int audioBestStreamId = mediaFile.getBestStreamId(Av::Stream::Type::Audio);
if (audioBestStreamId != -1)
parameters.addStream(audioBestStreamId);
Av::Encoding encoding;
switch (_encoding)
{
case Wt::WMediaPlayer::MP3: encoding = Av::Encoding::MP3; break;
case Wt::WMediaPlayer::FLA: encoding = Av::Encoding::FLA; break;
case Wt::WMediaPlayer::OGA: encoding = Av::Encoding::OGA; break;
case Wt::WMediaPlayer::WEBMA: encoding = Av::Encoding::WEBMA; break;
default:
encoding = Av::Encoding::MP3;
}
parameters.setEncoding(encoding);
_timeSlider->setDisabled(false);
_timeSlider->setRange(0, duration.total_seconds() );
_timeSlider->setValue(0);
_duration->setText( boost::posix_time::to_simple_string( duration ));
loadPlayer(path, parameters);
}
void void
AudioMediaPlayer::handlePlayOffset(int offsetSecs) AudioMediaPlayer::handlePlayOffset(int offsetSecs)
{ {
if (!_currentParameters) Av::TranscodeParameters parameters = _currentParameters;
return;
_currentParameters->setOffset( boost::posix_time::seconds(offsetSecs) ); parameters.setOffset( boost::posix_time::seconds(offsetSecs) );
loadPlayer(); loadPlayer(_currentFile, parameters);
_mediaPlayer->play();
} }
void void
@@ -262,10 +269,7 @@ AudioMediaPlayer::handleTrackEnded(void)
void void
AudioMediaPlayer::handleTimeUpdated(void) AudioMediaPlayer::handleTimeUpdated(void)
{ {
if (!_currentParameters) boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters.getOffset().total_seconds()));
return;
boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters->getOffset().total_seconds()));
_timeSlider->setValue( currentTime.total_seconds() ); _timeSlider->setValue( currentTime.total_seconds() );
_curTime->setText( boost::posix_time::to_simple_string( currentTime) ); _curTime->setText( boost::posix_time::to_simple_string( currentTime) );
+6 -3
View File
@@ -31,7 +31,7 @@
#include "database/Types.hpp" #include "database/Types.hpp"
#include "transcode/Parameters.hpp" #include "av/AvTranscoder.hpp"
#include "resource/AvConvTranscodeStreamResource.hpp" #include "resource/AvConvTranscodeStreamResource.hpp"
#include "resource/CoverResource.hpp" #include "resource/CoverResource.hpp"
@@ -70,7 +70,7 @@ class AudioMediaPlayer : public Wt::WContainerWidget
void handleVolumeSliderMoved(int value); void handleVolumeSliderMoved(int value);
void loadPlayer(void); void loadPlayer(boost::filesystem::path filePath, Av::TranscodeParameters& parameters);
// Signals // Signals
Wt::Signal<void> _playbackEnded; Wt::Signal<void> _playbackEnded;
@@ -90,7 +90,6 @@ class AudioMediaPlayer : public Wt::WContainerWidget
Wt::WText* _mediaArtistRelease; Wt::WText* _mediaArtistRelease;
// Controls // Controls
std::shared_ptr<Transcode::Parameters> _currentParameters;
Wt::WPushButton* _playBtn; Wt::WPushButton* _playBtn;
Wt::WPushButton* _pauseBtn; Wt::WPushButton* _pauseBtn;
Wt::WSlider* _timeSlider; Wt::WSlider* _timeSlider;
@@ -98,6 +97,10 @@ class AudioMediaPlayer : public Wt::WContainerWidget
Wt::WText* _curTime; Wt::WText* _curTime;
Wt::WText* _duration; Wt::WText* _duration;
// Transcode
boost::filesystem::path _currentFile;
Av::TranscodeParameters _currentParameters;
}; };
} // namespace Desktop } // namespace Desktop
+9 -9
View File
@@ -215,7 +215,7 @@ _playQueue(nullptr)
_mediaPlayer->loop().connect(boost::bind(&PlayQueue::setLoop,_playQueue, _1)); _mediaPlayer->loop().connect(boost::bind(&PlayQueue::setLoop,_playQueue, _1));
_playQueue->tracksUpdated().connect(std::bind([=] () { _playQueue->tracksUpdated().connect(std::bind([=] () {
LMS_LOG(MOD_UI, SEV_INFO) << "Playqueue updated!"; LMS_LOG(UI, INFO) << "Playqueue updated!";
playlistSaveFromPlayqueue(CurrentQueuePlaylistName); playlistSaveFromPlayqueue(CurrentQueuePlaylistName);
})); }));
@@ -308,14 +308,14 @@ Audio::playlistShowSaveDialog(std::string playlistName)
void void
Audio::playlistSaveFromPlayqueue(std::string playlistName) Audio::playlistSaveFromPlayqueue(std::string playlistName)
{ {
LMS_LOG(MOD_UI, SEV_INFO) << "Saving playqueue to playlist '" << playlistName << "'"; LMS_LOG(UI, INFO) << "Saving playqueue to playlist '" << playlistName << "'";
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
Playlist::pointer playlist = Playlist::get(DboSession(), playlistName, CurrentUser()); Playlist::pointer playlist = Playlist::get(DboSession(), playlistName, CurrentUser());
if (playlist) if (playlist)
{ {
LMS_LOG(MOD_UI, SEV_INFO) << "Erasing playlist '" << playlistName << "'"; LMS_LOG(UI, INFO) << "Erasing playlist '" << playlistName << "'";
playlist.remove(); playlist.remove();
} }
@@ -333,13 +333,13 @@ Audio::playlistSaveFromPlayqueue(std::string playlistName)
PlaylistEntry::create(DboSession(), track, playlist, pos++); PlaylistEntry::create(DboSession(), track, playlist, pos++);
} }
LMS_LOG(MOD_UI, SEV_INFO) << "Saving playqueue to playlist '" << playlistName << "' done. Contains " << pos << " entries"; LMS_LOG(UI, INFO) << "Saving playqueue to playlist '" << playlistName << "' done. Contains " << pos << " entries";
} }
void void
Audio::playlistLoadToPlayqueue(std::string playlistName) Audio::playlistLoadToPlayqueue(std::string playlistName)
{ {
LMS_LOG(MOD_UI, SEV_DEBUG) << "Loading playlist '" << playlistName << "' to playqueue"; LMS_LOG(UI, DEBUG) << "Loading playlist '" << playlistName << "' to playqueue";
std::vector<Track::id_type> entries; std::vector<Track::id_type> entries;
@@ -356,7 +356,7 @@ Audio::playlistLoadToPlayqueue(std::string playlistName)
_playQueue->clear(); _playQueue->clear();
_playQueue->addTracks(entries); _playQueue->addTracks(entries);
LMS_LOG(MOD_UI, SEV_DEBUG) << "Loading playlist '" << playlistName << "' to playqueue done. " << entries.size() << " entries"; LMS_LOG(UI, DEBUG) << "Loading playlist '" << playlistName << "' to playqueue done. " << entries.size() << " entries";
} }
@@ -395,7 +395,7 @@ Audio::playlistRefreshMenus()
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
// Clear playlists in each menu // Clear playlists in each menu
LMS_LOG(MOD_UI, SEV_DEBUG) << "Save item count: " << _popupMenuSave->count(); LMS_LOG(UI, DEBUG) << "Save item count: " << _popupMenuSave->count();
WPopupMenuClear(_popupMenuDelete); WPopupMenuClear(_popupMenuDelete);
WPopupMenuClear(_popupMenuLoad); WPopupMenuClear(_popupMenuLoad);
@@ -457,7 +457,7 @@ Audio::playSelectedTracks(PlayQueueAddType addType)
{ {
std::vector<Track::id_type> trackIds; std::vector<Track::id_type> trackIds;
LMS_LOG(MOD_UI, SEV_DEBUG) << "Playing selected tracks... nb selected = " << _trackView->getNbSelectedTracks() << ", add type = " << (addType == PlayQueueAddAllTracks ? "AddAll" : "AddSelected"); LMS_LOG(UI, DEBUG) << "Playing selected tracks... nb selected = " << _trackView->getNbSelectedTracks() << ", add type = " << (addType == PlayQueueAddAllTracks ? "AddAll" : "AddSelected");
_playQueue->clear(); _playQueue->clear();
@@ -472,7 +472,7 @@ Audio::playSelectedTracks(PlayQueueAddType addType)
break; break;
case PlayQueueAddSelectedTracks: case PlayQueueAddSelectedTracks:
LMS_LOG(MOD_UI, SEV_DEBUG) << "Adding selected tracks..."; LMS_LOG(UI, DEBUG) << "Adding selected tracks...";
// If nothing selected, get all the track and play everything // If nothing selected, get all the track and play everything
if (_trackView->getNbSelectedTracks() == 0) if (_trackView->getNbSelectedTracks() == 0)
+3 -6
View File
@@ -371,7 +371,7 @@ PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds)
{ {
using namespace Database; using namespace Database;
LMS_LOG(MOD_UI, SEV_DEBUG) << "Adding " << trackIds.size() << " tracks to play queue"; LMS_LOG(UI, DEBUG) << "Adding " << trackIds.size() << " tracks to play queue";
// Add tracks to model // Add tracks to model
for (Track::id_type trackId : trackIds) for (Track::id_type trackId : trackIds)
@@ -389,10 +389,7 @@ PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds)
_model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole); _model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole);
std::string coverUrl; std::string coverUrl;
if (track->getCoverType() != Track::CoverType::None) coverUrl = LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 64);
coverUrl = LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 64);
else
coverUrl = LmsApplication::instance()->getCoverResource()->getUnknownTrackUrl(64);
_model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole); _model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole);
_model->setData(dataRow, COLUMN_ID_COVER, std::string("playqueue-cover"), Wt::StyleClassRole); _model->setData(dataRow, COLUMN_ID_COVER, std::string("playqueue-cover"), Wt::StyleClassRole);
@@ -465,7 +462,7 @@ PlayQueue::playPrevious(void)
void void
PlayQueue::readTrack(int rowPos) PlayQueue::readTrack(int rowPos)
{ {
LMS_LOG(MOD_UI, SEV_DEBUG) << "Reading track at pos " << rowPos << ", row count = " << _model->rowCount(); LMS_LOG(UI, DEBUG) << "Reading track at pos " << rowPos << ", row count = " << _model->rowCount();
if (rowPos < _model->rowCount()) if (rowPos < _model->rowCount())
{ {
+4 -4
View File
@@ -150,7 +150,7 @@ TrackView::refresh(SearchFilter& filter)
void void
TrackView::getSelectedTracks(std::vector<Track::id_type>& track_ids) TrackView::getSelectedTracks(std::vector<Track::id_type>& track_ids)
{ {
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting selected tracks..."; LMS_LOG(UI, DEBUG) << "Getting selected tracks...";
Wt::WModelIndexSet indexSet = this->selectedIndexes(); Wt::WModelIndexSet indexSet = this->selectedIndexes();
@@ -164,7 +164,7 @@ TrackView::getSelectedTracks(std::vector<Track::id_type>& track_ids)
track_ids.push_back(id); track_ids.push_back(id);
} }
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all selected tracks: " << track_ids.size(); LMS_LOG(UI, DEBUG) << "Getting all selected tracks: " << track_ids.size();
} }
std::size_t std::size_t
@@ -192,7 +192,7 @@ TrackView::getFirstSelectedTrackPosition(void)
void void
TrackView::getTracks(std::vector<Track::id_type>& trackIds) TrackView::getTracks(std::vector<Track::id_type>& trackIds)
{ {
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks..."; LMS_LOG(UI, DEBUG) << "Getting all tracks...";
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::collection<Track::UIQueryResult> results = _queryModel.query(); Wt::Dbo::collection<Track::UIQueryResult> results = _queryModel.query();
@@ -203,7 +203,7 @@ TrackView::getTracks(std::vector<Track::id_type>& trackIds)
trackIds.push_back(id); trackIds.push_back(id);
} }
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks done! " << trackIds.size() << " tracks!"; LMS_LOG(UI, DEBUG) << "Getting all tracks done! " << trackIds.size() << " tracks!";
} }
} // namespace Desktop } // namespace Desktop
+13 -11
View File
@@ -156,7 +156,7 @@ Audio::Audio(Wt::WContainerWidget *parent)
trackSearch->trackPlay().connect(std::bind([=] (Track::id_type id) trackSearch->trackPlay().connect(std::bind([=] (Track::id_type id)
{ {
LMS_LOG(MOD_UI, SEV_DEBUG) << "Playing track id " << id; LMS_LOG(UI, DEBUG) << "Playing track id " << id;
// TODO reduce transaction scope here // TODO reduce transaction scope here
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
@@ -164,21 +164,23 @@ Audio::Audio(Wt::WContainerWidget *parent)
if (track) if (track)
{ {
Av::TranscodeParameters parameters;
// Determine the output format using the encoding of the player // Determine the output format using the encoding of the player
Transcode::Format::Encoding encoding; Av::Encoding encoding;
switch(mediaPlayer->getEncoding()) switch(mediaPlayer->getEncoding())
{ {
case Wt::WMediaPlayer::MP3: encoding = Transcode::Format::MP3; break; case Wt::WMediaPlayer::MP3: encoding = Av::Encoding::MP3; break;
case Wt::WMediaPlayer::FLA: encoding = Transcode::Format::FLA; break; case Wt::WMediaPlayer::FLA: encoding = Av::Encoding::FLA; break;
case Wt::WMediaPlayer::OGA: encoding = Transcode::Format::OGA; break; case Wt::WMediaPlayer::OGA: encoding = Av::Encoding::OGA; break;
case Wt::WMediaPlayer::WEBMA: encoding = Transcode::Format::WEBMA; break; case Wt::WMediaPlayer::WEBMA: encoding = Av::Encoding::WEBMA; break;
default: default:
encoding = Transcode::Format::MP3; encoding = Av::Encoding::MP3;
} }
// TODO compute parameters using user s profile parameters.setEncoding(encoding);
Transcode::InputMediaFile inputFile(track->getPath());
Transcode::Parameters parameters(inputFile, Transcode::Format::get(encoding)); // TODO compute parameters using user's profile
parameters.setBitrate(Transcode::Stream::Audio, 96000); parameters.setBitrate(Av::Stream::Type::Audio, 96000);
mediaPlayer->play(track.id(), parameters); mediaPlayer->play(track.id(), parameters);
} }
+14 -10
View File
@@ -80,17 +80,9 @@ _encoding(encoding)
} }
void void
AudioMediaPlayer::play(Database::Track::id_type trackId, const Transcode::Parameters& parameters) AudioMediaPlayer::play(Database::Track::id_type trackId, Av::TranscodeParameters parameters)
{ {
// FIXME memleak here boost::filesystem::path path;
AvConvTranscodeStreamResource *resource = new AvConvTranscodeStreamResource( parameters, this );
_player->clearSources();
_player->addSource( _encoding, Wt::WLink(resource));
_player->play();
_cover->setImageLink( Wt::WLink (LmsApplication::instance()->getCoverResource()->getTrackUrl(trackId, 48)));
{ {
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
@@ -99,7 +91,19 @@ AudioMediaPlayer::play(Database::Track::id_type trackId, const Transcode::Parame
_track->setText( Wt::WString::fromUTF8(track->getName() )); _track->setText( Wt::WString::fromUTF8(track->getName() ));
_artistRelease->setText( Wt::WString::fromUTF8(track->getArtist()->getName()) ); _artistRelease->setText( Wt::WString::fromUTF8(track->getArtist()->getName()) );
path = track->getPath();
} }
// FIXME memleak here
AvConvTranscodeStreamResource *resource = new AvConvTranscodeStreamResource( path, parameters, this );
_player->clearSources();
_player->addSource( _encoding, Wt::WLink(resource));
_player->play();
_cover->setImageLink( Wt::WLink (LmsApplication::instance()->getCoverResource()->getTrackUrl(trackId, 48)));
} }
} // namespace UserInterface } // namespace UserInterface
@@ -25,7 +25,7 @@
#include <Wt/WImage> #include <Wt/WImage>
#include "database/DatabaseHandler.hpp" #include "database/DatabaseHandler.hpp"
#include "transcode/Parameters.hpp" #include "av/AvTranscoder.hpp"
namespace UserInterface { namespace UserInterface {
namespace Mobile { namespace Mobile {
@@ -38,7 +38,7 @@ class AudioMediaPlayer : public Wt::WContainerWidget
AudioMediaPlayer(Wt::WMediaPlayer::Encoding encoding, Wt::WContainerWidget *parent = 0); AudioMediaPlayer(Wt::WMediaPlayer::Encoding encoding, Wt::WContainerWidget *parent = 0);
void play(Database::Track::id_type trackId, const Transcode::Parameters& parameters); void play(Database::Track::id_type trackId, Av::TranscodeParameters parameters);
Wt::WMediaPlayer::Encoding getEncoding() const { return _encoding; } Wt::WMediaPlayer::Encoding getEncoding() const { return _encoding; }
@@ -27,16 +27,17 @@
namespace UserInterface { namespace UserInterface {
AvConvTranscodeStreamResource::AvConvTranscodeStreamResource(const Transcode::Parameters& parameters, Wt::WObject *parent) AvConvTranscodeStreamResource::AvConvTranscodeStreamResource(boost::filesystem::path p, Av::TranscodeParameters parameters, Wt::WObject *parent)
: Wt::WResource(parent), : Wt::WResource(parent),
_parameters( parameters ) _filePath(p),
_parameters( parameters )
{ {
LMS_LOG(MOD_UI, SEV_DEBUG) << "CONSTRUCTING RESOURCE"; LMS_LOG(UI, DEBUG) << "CONSTRUCTING RESOURCE";
} }
AvConvTranscodeStreamResource::~AvConvTranscodeStreamResource() AvConvTranscodeStreamResource::~AvConvTranscodeStreamResource()
{ {
LMS_LOG(MOD_UI, SEV_DEBUG) << "DESTRUCTING RESOURCE"; LMS_LOG(UI, DEBUG) << "DESTRUCTING RESOURCE";
beingDeleted(); beingDeleted();
} }
@@ -47,19 +48,25 @@ AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request,
// see if this request is for a continuation: // see if this request is for a continuation:
Wt::Http::ResponseContinuation *continuation = request.continuation(); Wt::Http::ResponseContinuation *continuation = request.continuation();
LMS_LOG(MOD_UI, SEV_DEBUG) << "Handling new request. Continuation = " << continuation; LMS_LOG(UI, DEBUG) << "Handling new request. Continuation = " << continuation;
std::shared_ptr<Transcode::AvConvTranscoder> transcoder; std::shared_ptr<Av::Transcoder> transcoder;
if (continuation) if (continuation)
transcoder = boost::any_cast<std::shared_ptr<Transcode::AvConvTranscoder> >(continuation->data()); transcoder = boost::any_cast<std::shared_ptr<Av::Transcoder> >(continuation->data());
if (!transcoder) if (!transcoder)
{ {
LMS_LOG(MOD_UI, SEV_DEBUG) << "Launching transcoder"; LMS_LOG(UI, DEBUG) << "Launching transcoder";
transcoder = std::make_shared<Transcode::AvConvTranscoder>( _parameters); transcoder = std::make_shared<Av::Transcoder>( _filePath, _parameters);
LMS_LOG(MOD_UI, SEV_DEBUG) << "Mime type set to '" << _parameters.getOutputFormat().getMimeType() << "'"; LMS_LOG(UI, DEBUG) << "Mime type set to '" << Av::encoding_to_mimetype(_parameters.getEncoding());
response.setMimeType(_parameters.getOutputFormat().getMimeType()); response.setMimeType( Av::encoding_to_mimetype(_parameters.getEncoding()) );
if (!transcoder->start())
{
LMS_LOG(UI, ERROR) << "Cannot start transcoder";
return;
}
} }
if (!transcoder->isComplete()) if (!transcoder->isComplete())
@@ -72,10 +79,10 @@ AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request,
// Give the client all the output data // Give the client all the output data
response.out().write(reinterpret_cast<char*>(&data[0]), data.size()); response.out().write(reinterpret_cast<char*>(&data[0]), data.size());
LMS_LOG(MOD_UI, SEV_DEBUG) << "Written " << data.size() << " bytes! complete = " << std::boolalpha << transcoder->isComplete() << ", produced bytes = " << transcoder->getOutputBytes(); LMS_LOG(UI, DEBUG) << "Written " << data.size() << " bytes! complete = " << std::boolalpha << transcoder->isComplete();
if (!response.out()) if (!response.out())
LMS_LOG(MOD_UI, SEV_ERROR) << "Write failed!"; LMS_LOG(UI, ERROR) << "Write failed!";
} }
if (!transcoder->isComplete() && response.out()) { if (!transcoder->isComplete() && response.out()) {
@@ -83,7 +90,7 @@ AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request,
continuation->setData(transcoder); continuation->setData(transcoder);
} }
else else
LMS_LOG(MOD_UI, SEV_DEBUG) << "No more data!"; LMS_LOG(UI, DEBUG) << "No more data!";
} }
} // namespace UserInterface } // namespace UserInterface
@@ -25,23 +25,22 @@
#include <Wt/WResource> #include <Wt/WResource>
#include "transcode/AvConvTranscoder.hpp" #include "av/AvTranscoder.hpp"
#include "transcode/Parameters.hpp"
namespace UserInterface { namespace UserInterface {
class AvConvTranscodeStreamResource : public Wt::WResource class AvConvTranscodeStreamResource : public Wt::WResource
{ {
public: public:
AvConvTranscodeStreamResource(const Transcode::Parameters& parameters, Wt::WObject *parent = 0); AvConvTranscodeStreamResource(boost::filesystem::path p, Av::TranscodeParameters parameters, Wt::WObject *parent = 0);
~AvConvTranscodeStreamResource(); ~AvConvTranscodeStreamResource();
void handleRequest(const Wt::Http::Request& request, void handleRequest(const Wt::Http::Request& request,
Wt::Http::Response& response); Wt::Http::Response& response);
private: private:
Transcode::Parameters _parameters; boost::filesystem::path _filePath;
std::shared_ptr<Transcode::AvConvTranscoder> _transcoder; Av::TranscodeParameters _parameters;
static const std::size_t _bufferSize = 8192*16; static const std::size_t _bufferSize = 8192*16;
}; };
+18 -26
View File
@@ -41,29 +41,21 @@ CoverResource:: ~CoverResource()
beingDeleted(); beingDeleted();
} }
const CoverArt::CoverArt& const Image::Image&
CoverResource::getDefaultCover(std::size_t size) CoverResource::getDefaultCover(std::size_t size)
{ {
auto itCover = _defaultCovers.find(size); auto itCover = _defaultCovers.find(size);
if (itCover == _defaultCovers.end()) if (itCover == _defaultCovers.end())
{ {
// Load default cover art for this size // Load default cover art for this size
Image::Image image;
CoverArt::CoverArt defaultCover; if (!image.load( Wt::WApplication::instance()->docRoot() + unknownCoverPath ))
throw std::runtime_error("Cannot read default cover file");
std::vector<unsigned char> data; image.scale(size);
{
std::ifstream ist(Wt::WApplication::instance()->docRoot() + unknownCoverPath);
char c;
while(ist.get(c))
data.push_back(c);
}
defaultCover.setData(data); auto res = _defaultCovers.insert(std::make_pair(size, image));
defaultCover.setMimeType("image/jpeg");
defaultCover.scale(size);
auto res = _defaultCovers.insert(std::make_pair(size, defaultCover));
itCover = res.first; itCover = res.first;
} }
@@ -89,11 +81,14 @@ CoverResource::getUnknownTrackUrl(size_t size) const
} }
void void
CoverResource::putCover(Wt::Http::Response& response, const CoverArt::CoverArt& cover) CoverResource::putCover(Wt::Http::Response& response, Image::Image cover)
{ {
response.setMimeType( cover.getMimeType() ); std::vector<unsigned char> data;
BOOST_FOREACH(unsigned char c, cover.getData())
response.out().put( c ); cover.save(data, Image::Format::JPEG);
response.setMimeType( Image::format_to_mimeType(Image::Format::JPEG) );
response.out().write(reinterpret_cast<const char *>(&data[0]), data.size());
} }
void void
@@ -106,7 +101,7 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
try try
{ {
std::vector<CoverArt::CoverArt> covers; std::vector<Image::Image> covers;
// Mandatory parameter size // Mandatory parameter size
if (!sizeStr) if (!sizeStr)
@@ -142,17 +137,14 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
covers = CoverArt::Grabber::instance().getFromTrack(path); covers = CoverArt::Grabber::instance().getFromTrack(path);
break; break;
case Database::Track::CoverType::ExternalFile:
covers = CoverArt::Grabber::instance().getFromDirectory(path.parent_path());
break;
case Database::Track::CoverType::None: case Database::Track::CoverType::None:
covers = CoverArt::Grabber::instance().getFromDirectory(path.parent_path());
break; break;
} }
} }
else if (releaseIdStr) else if (releaseIdStr)
{ {
Database::Release::id_type releaseId = std::stol(*releaseIdStr); // TODO try catch Database::Release::id_type releaseId = std::stol(*releaseIdStr);
// transactions are not thread safe // transactions are not thread safe
std::unique_lock<std::mutex> lock(_mutex); std::unique_lock<std::mutex> lock(_mutex);
Wt::Dbo::Transaction transaction(_db.getSession()); Wt::Dbo::Transaction transaction(_db.getSession());
@@ -160,7 +152,7 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), releaseId); covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), releaseId);
} }
for (CoverArt::CoverArt& cover : covers) for (Image::Image& cover : covers)
{ {
if (cover.scale(size)) if (cover.scale(size))
{ {
@@ -175,7 +167,7 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
} }
catch (std::invalid_argument& e) catch (std::invalid_argument& e)
{ {
LMS_LOG(MOD_UI, SEV_ERROR) << "Invalid argument: " << e.what(); LMS_LOG(UI, ERROR) << "Invalid argument: " << e.what();
} }
} }
+4 -4
View File
@@ -25,7 +25,7 @@
#include <Wt/WResource> #include <Wt/WResource>
#include "database/DatabaseHandler.hpp" #include "database/DatabaseHandler.hpp"
#include "cover/CoverArt.hpp" #include "image/Image.hpp"
namespace UserInterface { namespace UserInterface {
@@ -47,14 +47,14 @@ class CoverResource : public Wt::WResource
private: private:
const CoverArt::CoverArt& getDefaultCover(std::size_t size); const Image::Image& getDefaultCover(std::size_t size);
void putCover(Wt::Http::Response& response, const CoverArt::CoverArt& cover); void putCover(Wt::Http::Response& response, Image::Image image);
std::mutex _mutex; std::mutex _mutex;
Database::Handler& _db; Database::Handler& _db;
// Default cover for different sizes // Default cover for different sizes
std::map<std::size_t, CoverArt::CoverArt> _defaultCovers; std::map<std::size_t, Image::Image> _defaultCovers;
// TODO construct a cache for covers? // TODO construct a cache for covers?
}; };
+2 -2
View File
@@ -91,7 +91,7 @@ Settings::Settings(Wt::WContainerWidget* parent)
void void
Settings::handleDatabaseDirectoriesChanged() Settings::handleDatabaseDirectoriesChanged()
{ {
LMS_LOG(MOD_UI, SEV_NOTICE) << "Media directories have changed: requesting imediate scan"; LMS_LOG(UI, INFO) << "Media directories have changed: requesting imediate scan";
// On directory add or delete, request an immediate scan // On directory add or delete, request an immediate scan
{ {
Wt::Dbo::Transaction transaction(DboSession()); Wt::Dbo::Transaction transaction(DboSession());
@@ -107,7 +107,7 @@ Settings::restartDatabaseUpdateService()
// Restarting the update service // Restarting the update service
boost::lock_guard<boost::mutex> serviceLock (Service::ServiceManager::instance().mutex()); boost::lock_guard<boost::mutex> serviceLock (Service::ServiceManager::instance().mutex());
Service::DatabaseUpdateService::pointer service = Service::ServiceManager::instance().getService<Service::DatabaseUpdateService>(); Service::DatabaseUpdateService::pointer service = Service::ServiceManager::instance().get<Service::DatabaseUpdateService>();
if (service) if (service)
service->restart(); service->restart();
} }
+1 -1
View File
@@ -116,7 +116,7 @@ class AccountFormModel : public Wt::WFormModel
} }
catch(Wt::Dbo::Exception& exception) catch(Wt::Dbo::Exception& exception)
{ {
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what(); LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what();
return false; return false;
} }
+1 -1
View File
@@ -100,7 +100,7 @@ class AudioFormModel : public Wt::WFormModel
} }
catch(Wt::Dbo::Exception& exception) catch(Wt::Dbo::Exception& exception)
{ {
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what(); LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what();
return false; return false;
} }
+1 -1
View File
@@ -106,7 +106,7 @@ class DatabaseFormModel : public Wt::WFormModel
} }
catch(Wt::Dbo::Exception& exception) catch(Wt::Dbo::Exception& exception)
{ {
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what(); LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what();
return false; return false;
} }
@@ -67,7 +67,7 @@ class FirstConnectionFormModel : public Wt::WFormModel
// If it's the case, just do nothing // If it's the case, just do nothing
if (!Database::User::getAll(DboSession()).empty()) if (!Database::User::getAll(DboSession()).empty())
{ {
LMS_LOG(MOD_UI, SEV_ERROR) << "Admin user already created"; LMS_LOG(UI, ERROR) << "Admin user already created";
error = Wt::WString("Admin user already created!"); error = Wt::WString("Admin user already created!");
return false; return false;
} }
@@ -86,7 +86,7 @@ class FirstConnectionFormModel : public Wt::WFormModel
} }
catch(Wt::Dbo::Exception& exception) catch(Wt::Dbo::Exception& exception)
{ {
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what(); LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what();
error = Wt::WString(exception.what()); error = Wt::WString(exception.what());
return false; return false;
} }
@@ -81,7 +81,7 @@ class MediaDirectoryFormModel : public Wt::WFormModel
} }
catch(Wt::Dbo::Exception& exception) catch(Wt::Dbo::Exception& exception)
{ {
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what(); LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what();
return false; return false;
} }
+3 -3
View File
@@ -168,12 +168,12 @@ class UserFormModel : public Wt::WFormModel
// user may have been deleted by someone else // user may have been deleted by someone else
if (!authUser.isValid()) { if (!authUser.isValid()) {
LMS_LOG(MOD_UI, SEV_ERROR) << "user identity does not exist!"; LMS_LOG(UI, ERROR) << "user identity does not exist!";
return false; return false;
} }
else if(!user) else if(!user)
{ {
LMS_LOG(MOD_UI, SEV_ERROR) << "User not found!"; LMS_LOG(UI, ERROR) << "User not found!";
return false; return false;
} }
@@ -206,7 +206,7 @@ class UserFormModel : public Wt::WFormModel
} }
catch(Wt::Dbo::Exception& exception) catch(Wt::Dbo::Exception& exception)
{ {
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what(); LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what();
return false; return false;
} }
+2 -2
View File
@@ -96,12 +96,12 @@ Users::refresh(void)
} }
catch(Wt::Dbo::Exception& e) catch(Wt::Dbo::Exception& e)
{ {
LMS_LOG(MOD_UI, SEV_ERROR) << "Caught exception when getting userId=" << userId << ": " << e.code(); LMS_LOG(UI, ERROR) << "Caught exception when getting userId=" << userId << ": " << e.code();
continue; continue;
} }
if (!authUser.isValid()) { if (!authUser.isValid()) {
LMS_LOG(MOD_UI, SEV_ERROR) << "Users::refresh: skipping invalid userId = " << userId; LMS_LOG(UI, ERROR) << "Users::refresh: skipping invalid userId = " << userId;
continue; continue;
} }
-2
View File
@@ -25,8 +25,6 @@
#include <Wt/WMediaPlayer> #include <Wt/WMediaPlayer>
#include <Wt/WFileResource> #include <Wt/WFileResource>
#include "transcode/Parameters.hpp"
#include "LmsApplication.hpp" #include "LmsApplication.hpp"
#include "VideoDatabaseWidget.hpp" #include "VideoDatabaseWidget.hpp"
+27 -30
View File
@@ -17,8 +17,7 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <boost/date_time/posix_time/posix_time.hpp> //include all types plus i/o
#include <boost/foreach.hpp>
#include <Wt/WMediaPlayer> #include <Wt/WMediaPlayer>
@@ -29,25 +28,25 @@
namespace UserInterface { namespace UserInterface {
Wt::WMediaPlayer::Encoding Wt::WMediaPlayer::Encoding
convert(Transcode::Format format) AvEncoding_to_WtEncoding(Av::Encoding encoding)
{ {
switch( format.getEncoding() ) switch( encoding )
{ {
case Transcode::Format::OGA: return Wt::WMediaPlayer::OGA; case Av::Encoding::OGA: return Wt::WMediaPlayer::OGA;
case Transcode::Format::OGV: return Wt::WMediaPlayer::OGV; case Av::Encoding::OGV: return Wt::WMediaPlayer::OGV;
case Transcode::Format::MP3: return Wt::WMediaPlayer::MP3; case Av::Encoding::MP3: return Wt::WMediaPlayer::MP3;
case Transcode::Format::WEBMA: return Wt::WMediaPlayer::WEBMA; case Av::Encoding::WEBMA: return Wt::WMediaPlayer::WEBMA;
case Transcode::Format::WEBMV: return Wt::WMediaPlayer::WEBMV; case Av::Encoding::WEBMV: return Wt::WMediaPlayer::WEBMV;
case Transcode::Format::FLA: return Wt::WMediaPlayer::FLA; case Av::Encoding::FLA: return Wt::WMediaPlayer::FLA;
case Transcode::Format::FLV: return Wt::WMediaPlayer::FLV; case Av::Encoding::FLV: return Wt::WMediaPlayer::FLV;
case Transcode::Format::M4A: return Wt::WMediaPlayer::M4A; case Av::Encoding::M4A: return Wt::WMediaPlayer::M4A;
case Transcode::Format::M4V: return Wt::WMediaPlayer::M4V; case Av::Encoding::M4V: return Wt::WMediaPlayer::M4V;
} }
assert(0); assert(0);
} }
VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Transcode::Parameters& parameters, Wt::WContainerWidget *parent) VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent), : Wt::WContainerWidget(parent),
_mediaResource(nullptr), _mediaResource(nullptr),
_currentParameters(parameters), _currentParameters(parameters),
@@ -102,11 +101,16 @@ VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Transcode::Parameters& par
Wt::WPushButton* parametersButton = new Wt::WPushButton("Parameters", this); Wt::WPushButton* parametersButton = new Wt::WPushButton("Parameters", this);
parametersButton->clicked().connect( this, &VideoMediaPlayerWidget::handleParametersEdit ); parametersButton->clicked().connect( this, &VideoMediaPlayerWidget::handleParametersEdit );
_currentFile = mediaFile.getPath();
load(parameters); load(parameters);
_timeSlider->setRange(0, mediaFile.getDuration().total_seconds() );
_duration->setText( boost::posix_time::to_simple_string( mediaFile.getDuration() ));
} }
void void
VideoMediaPlayerWidget::load(const Transcode::Parameters& parameters) VideoMediaPlayerWidget::load(Av::TranscodeParameters parameters)
{ {
_mediaPlayer->clearSources(); _mediaPlayer->clearSources();
@@ -116,15 +120,13 @@ VideoMediaPlayerWidget::load(const Transcode::Parameters& parameters)
if (_mediaResource) if (_mediaResource)
delete _mediaResource; delete _mediaResource;
_mediaResource = new AvConvTranscodeStreamResource( parameters, this ); _mediaResource = new AvConvTranscodeStreamResource( _currentFile, parameters, this );
_mediaInternalLink.setResource( _mediaResource ); _mediaInternalLink.setResource( _mediaResource );
_mediaPlayer->addSource( convert(parameters.getOutputFormat()), _mediaInternalLink ); _mediaPlayer->addSource( AvEncoding_to_WtEncoding(parameters.getEncoding()), _mediaInternalLink );
_timeSlider->setRange(0, parameters.getInputMediaFile().getDuration().total_seconds() ); _timeSlider->setValue( 0 );
_timeSlider->setValue( parameters.getOffset().total_seconds() );
_duration->setText( boost::posix_time::to_simple_string( parameters.getInputMediaFile().getDuration() ));
_mediaPlayer->play(); _mediaPlayer->play();
} }
@@ -132,7 +134,6 @@ VideoMediaPlayerWidget::load(const Transcode::Parameters& parameters)
void void
VideoMediaPlayerWidget::handlePlayOffset(int offsetSecs) VideoMediaPlayerWidget::handlePlayOffset(int offsetSecs)
{ {
std::cout << "Want to play at offset " << offsetSecs << std::endl;;
_currentParameters.setOffset( boost::posix_time::seconds(offsetSecs) ); _currentParameters.setOffset( boost::posix_time::seconds(offsetSecs) );
load( _currentParameters ); load( _currentParameters );
@@ -142,22 +143,17 @@ VideoMediaPlayerWidget::handlePlayOffset(int offsetSecs)
void void
VideoMediaPlayerWidget::handleSliderMoved(int value) VideoMediaPlayerWidget::handleSliderMoved(int value)
{ {
std::cout << "Slider moved to " << value << std::endl;
_curTime->setText( boost::posix_time::to_simple_string( boost::posix_time::seconds( value ) ) ); _curTime->setText( boost::posix_time::to_simple_string( boost::posix_time::seconds( value ) ) );
} }
void void
VideoMediaPlayerWidget::handleTimeUpdated(void) VideoMediaPlayerWidget::handleTimeUpdated(void)
{ {
std::cout << "Time updated to " << _mediaPlayer->currentTime() << std::endl;
if (_mediaPlayer->currentTime() > 0 && _mediaPlayer->currentTime() < _currentParameters.getInputMediaFile().getDuration().total_seconds()) boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters.getOffset().total_seconds()));
{
boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters.getOffset().total_seconds()));
_timeSlider->setValue( currentTime.total_seconds() ); _timeSlider->setValue( currentTime.total_seconds() );
_curTime->setText( boost::posix_time::to_simple_string( currentTime) ); _curTime->setText( boost::posix_time::to_simple_string( currentTime) );
}
} }
void void
@@ -175,13 +171,14 @@ VideoMediaPlayerWidget::handleClose(void)
void void
VideoMediaPlayerWidget::handleParametersEdit(void) VideoMediaPlayerWidget::handleParametersEdit(void)
{ {
/*
_dialog = new VideoParametersDialog("Parameters"); _dialog = new VideoParametersDialog("Parameters");
_dialog->load(_currentParameters); _dialog->load(_currentParameters);
_dialog->show(); _dialog->show();
_dialog->finished().connect(this, &VideoMediaPlayerWidget::handleParametersDone); _dialog->finished().connect(this, &VideoMediaPlayerWidget::handleParametersDone);
*/
} }
void void
+6 -6
View File
@@ -28,7 +28,7 @@
#include "VideoParametersDialog.hpp" #include "VideoParametersDialog.hpp"
#include "transcode/Parameters.hpp" #include "av/AvTranscoder.hpp"
#include "resource/AvConvTranscodeStreamResource.hpp" #include "resource/AvConvTranscodeStreamResource.hpp"
namespace UserInterface { namespace UserInterface {
@@ -37,18 +37,16 @@ class VideoMediaPlayerWidget : public Wt::WContainerWidget
{ {
public: public:
VideoMediaPlayerWidget( const Transcode::Parameters& parameters, Wt::WContainerWidget *parent = 0); VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent = 0);
Wt::Signal<void>& close() { return _close; }; Wt::Signal<void>& close() { return _close; };
private: private:
// Signals // Signals
Wt::Signal<void> _close; Wt::Signal<void> _close;
void load(const Transcode::Parameters& parameters); void load(Av::TranscodeParameters parameters);
// Player controls // Player controls
void handlePlayOffset(int offsetSecs); void handlePlayOffset(int offsetSecs);
@@ -62,13 +60,15 @@ class VideoMediaPlayerWidget : public Wt::WContainerWidget
void handleParametersEdit(void); void handleParametersEdit(void);
void handleParametersDone(Wt::WDialog::DialogCode); void handleParametersDone(Wt::WDialog::DialogCode);
boost::filesystem::path _currentFile;
// Core // Core
Wt::WMediaPlayer* _mediaPlayer; Wt::WMediaPlayer* _mediaPlayer;
AvConvTranscodeStreamResource* _mediaResource; AvConvTranscodeStreamResource* _mediaResource;
Wt::WLink _mediaInternalLink; Wt::WLink _mediaInternalLink;
// Controls // Controls
Transcode::Parameters _currentParameters; Av::TranscodeParameters _currentParameters;
Wt::WPushButton* _playBtn; Wt::WPushButton* _playBtn;
Wt::WPushButton* _pauseBtn; Wt::WPushButton* _pauseBtn;
Wt::WSlider* _timeSlider; Wt::WSlider* _timeSlider;

Some files were not shown because too many files have changed in this diff Show More