Merge branch 'develop'

This commit is contained in:
emeric
2015-11-11 21:47:15 +01:00
292 changed files with 2529 additions and 27734 deletions
+20
View File
@@ -3,3 +3,23 @@ AUTOMAKE_OPTIONS = dist-bzip2 no-dist-gzip
SUBDIRS = test src
lms_docrootdir=$(pkgdatadir)/docroot
lms_approotdir=$(pkgdatadir)/approot
lms_cssdir=$(lms_docrootdir)/css
lms_imagesdir=$(lms_docrootdir)/images
lms_css_DATA = \
docroot/css/lms.css
lms_images_DATA = \
docroot/images/unknown-cover.jpg
lms_approot_DATA = \
approot/templates.xml
install-data-hook:
ln -s $(datadir)/Wt/resources $(lms_docrootdir)/resources || echo "Skipped symlink creation"
uninstall-local:
cd $(lms_docrootdir) && rm -f -r $(lms_docrootdir)/resources
+1 -70
View File
@@ -1,70 +1 @@
LMS - Lightweight Media Server
LMS is a self-hosted media streaming software, released under the GPLv3 license.
It allows you to access your music and your videos using an https web interface.
Features:
- Winamp-like interface, suited for large databases
- Audio/Video transcode for maximum interoperability and low bandwith requirements
- User management
- LMS API available in Protocol Buffers (see src/lms-api/proto/messages.proto)
Please note LMS is still under development and will gain more features in the future.
A mobile application based on Kivy is being developped as well.
LMS is written entirely in C++. Therefore, it is suitable to run on embedded devices, where space and/or memory are limited.
Please note some media files may require significant CPU usage to be transcoded.
I) DEPENDENCIES
Debian packages:
g++ autoconf automake libboost-dev libavcodec-dev libwtdbosqlite-dev libwthttp-dev libwtdbo-dev libwt-dev libprotobuf-dev libconfig++-dev libprotobuf-dev protobuf-compiler libjpeg8-dev libavcodec-dev libavformat-dev libavutil-dev
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)
II) BUILD
# autoreconf -vfi
# mkdir build
# cd build
# ../configure
(configure will complain if a mandatory library is missing)
The configure script allows you to disable the LMS API support (and protobuf dependencies)
See 'configure --help' for more information.
# make
III) Setting up SSL materials (required by the SSL server and the web server)
This is just a self signed certificate example, you could do use a CA if you want.
Generate a dh file:
# openssl dhparam -out dh2048.pem 2048
Generate a self signed certificate:
# openssl req -x509 -out cert.pem -keyout privkey.pem -newkey rsa:4096
IV) RUNNING
In the build directory:
# ./src/lms lms.conf
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:
# 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.
V) LINKS
- Wt (http://www.webtoolkit.eu/)
- bootstrap3 (http://getbootstrap.com/)
- libav project (https://www.libav.org/).
- Protocol Buffers (https://github.com/google/protobuf/)
See README.md
+73
View File
@@ -0,0 +1,73 @@
# LMS - Lightweight Media Server
LMS is a self-hosted media streaming software, released under the GPLv3 license.
It allows you to access your music and your videos using an https web interface.
## Features
- Winamp-like interface, suited for large databases
- Audio/Video transcode for maximum interoperability and low bandwith requirements
- User management
Please note LMS is still under development and will gain more features in the future.
LMS is written entirely in C++. Therefore, it is suitable to run on embedded devices, where space and/or memory are limited.
Please note some media files may require significant CPU usage to be transcoded.
## Dependencies
### Debian or Ubuntu packages
```sh
$ apt-get install g++ autoconf automake libboost-dev libboost-locale-dev libboost-iostreams-dev libavcodec-dev libwtdbosqlite-dev libwthttp-dev libwtdbo-dev libwt-dev libmagick++-dev libavcodec-dev libavformat-dev libav-tools libpstreams-dev
```
## Build
```sh
$ autoreconf -vfi
$ mkdir build
$ cd build
$ ../configure --prefix=/usr
```
configure will complain if a mandatory library is missing.
```sh
$ make -j 4
```
## Install
```sh
$ make install
```
This command requires root privileges
## Running
```sh
$ /usr/bin/lms --docroot='/usr/share/lms/docroot/;/resources,/css,/images' --approot=/usr/share/lms/approot --http-address 0.0.0.0 --http-port 5081
```
It is highly recommended to run LMS as a non root user.
The exectuable needs write accesses to the /var/lms/ directory.
## Setting up SSL materials (optional)
Here is just a self signed certificate example, you could do use a CA if you want.
Generate a dh file:
```sh
$ openssl dhparam -out dh2048.pem 2048
```
Generate a self signed certificate:
```sh
$ openssl req -x509 -out cert.pem -keyout privkey.pem -newkey rsa:4096
```
Run:
```sh
$ /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
```
Depending on your SSL parameters, you may be asked for the PEM passphrase to unlock the private key.
## Credits
- Wt (http://www.webtoolkit.eu/)
- bootstrap3 (http://getbootstrap.com/)
- libav project (https://www.libav.org/).
+9 -18
View File
@@ -3,16 +3,17 @@
- Make the feature
[Cover]
- Scaling: find something more "reliable" than GIL and its custom extensions (adobe work, io_new)?
- Handle several file formats (not only jpg)
- Handle preferred cover file names
- Implement a cache and a grabber from some web service (mandatory for artists)
[Database]
- Optim, use SQL query to get the "genre" orphans
- Use Inotify like system to watch modified/added files?
- count scan import errors
- handle access rights problems (instead of aborting)
- add a global play counter for tracks. This will help people to spot most popular files
- rework the exception process in av/metadata/updater in case of bad files
- Use the WServer::post method to notify the end of the database scan? (with results?)
- Put more config information in the database, access from the UI
[Metadata]
- OGG metadata -> properly handle metadata nested in the audio stream
@@ -22,23 +23,18 @@
[Users]
- Handle login lifetime?
- Limit multi login from UI and remote interfaces (limit per interface is acceptable)
[ServiceManager]
- Rework the whole start/stop/try/cach/thread/interrupts things
- Use our own WIOService
- Use the WServer::post method to kick users already logged in?
- Limit multi login from UI and API interfaces (limit per interface is acceptable)
[Services]
- consider adding support for external web servers (new FCGI service?)
[Transcode]
- some early end of playback spotted on files. Maybe this is because the stderr of the forked process is not properly closed?
[UI]
- handle internationalization
[Settings]
- logout users that are being changed (loss of admin admin rights), or make sure they are still admin when they make changes
- "signal not exposed" problem if a user logout and login again. Bad resource destruction?
- add a scroll area in order for settings to work on mobile devices
[user/transcoding]
- Prefered codecs for video
- use a slider instead of a combo box for the bitrates?
@@ -52,9 +48,7 @@
[Audio]
- Style eveything nicely...
- MediaPlayer: move the slider using js (http://redmine.webtoolkit.eu/boards/2/topics/7924?r=8478, http://redmine.emweb.be/boards/2/topics/10994)
- TrackView: handle durations > 1 hour
- TrackView: Reselect the current selected item when displaying the updated search results
- TrackView: display the total duration of the track query
- Add covers in the release filter?
- Add a download button to get the current playlist in a streamed zip file
- Add a upload button to upload media files in a dedicated directory
@@ -69,11 +63,8 @@
- Make a dedicated audio layout in mobile environment (keep the same layout for videos?)
- Handle internal path
[Lms API]
- Implement partial text search options in the GetXXX messages
[REST API]
- Make another dedicated REST API. Maybe use the SubSonic API or Ampache API?
- Make a dedicated REST API. Maybe use the SubSonic API or Ampache API?
[Logs]
- Capture transcoder log output to get more information on errors
- Capture transcoder log output to get more information on errors?
@@ -311,6 +311,30 @@
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:audio-file-extensions}">
Audio file extensions
</label>
<div class="col-sm-5">
${audio-file-extensions}
</div>
<div class="help-block col-sm-5">
${audio-file-extensions-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:video-file-extensions}">
Video file extensions
</label>
<div class="col-sm-5">
${video-file-extensions}
</div>
<div class="help-block col-sm-5">
${video-file-extensions-info}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
${apply-button} ${discard-button} ${immediate-scan-button}
-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";
}
}
+31 -92
View File
@@ -8,55 +8,46 @@ AC_LANG_CPLUSPLUS
# Checks for programs
AC_PROG_CXX
AC_ARG_ENABLE([video], AS_HELP_STRING([--enable-video],
[Enable Video support. Default to no.])],
[enable_video="$enableval"],
[enable_video="no"])
AC_ARG_ENABLE(lmsapi, AS_HELP_STRING([--enable-lmsapi],
[Build with the lmsapi protocol support. Defaults to yes.])],
[enable_lmsapi="$enableval"],
[enable_lmsapi="yes"])
AM_CONDITIONAL([VIDEO], [test x$enable_video = xyes])
AS_IF([test x$enable_video = xyes], [AC_DEFINE(HAVE_VIDEO, [1], [Enable Video support])])
# ProtoBuf compiler.
# First, specify with --with-protoc=/path/of/protoc.
# Or, specify with env variable PROTOC.
# If neither of the above, find it in the path.
AS_IF([test "$enable_lmsapi" = "yes"],
[AC_ARG_WITH([protoc],
[AS_HELP_STRING([--with-protoc=/path/of/protoc],
[Location of the protocol buffers compiler protoc. Defaults to looking on path.])],
[PROTOC="$withval"],
[ AS_IF([test "x${PROTOC}" == "x"],
[AC_PATH_PROG([PROTOC], [protoc], [no])])]
)
]
[ AS_IF([test "${PROTOC}" == "no"], [AC_MSG_ERROR([ProtoBuf compiler "protoc" not found.])]) ]
[AC_CHECK_LIB( [protobuf],
[main],
,
[AC_MSG_ERROR([libprotobuf not found!])])]
[lmsapi=true]
,
[lmsapi=false])
PKG_CHECK_MODULES(IMAGEMAGICKXX, "ImageMagick++", [ HAVE_IMAGEMAGICKXX=yes ], [ ])
if test -n "$HAVE_IMAGEMAGICKXX"; then
MAGICKXX_CFLAGS="$IMAGEMAGICKXX_CFLAGS"
MAGICKXX_LIBS="$IMAGEMAGICKXX_LIBS"
fi
AC_SUBST(MAGICKXX_CFLAGS)
AC_SUBST(MAGICKXX_LIBS)
AM_CONDITIONAL([LMSAPI], [test x$lmsapi = xtrue])
AS_IF([test x$lmsapi = xtrue], [AC_DEFINE(HAVE_LMSAPI, [1], [Enable LMS API support])])
AC_CHECK_HEADERS([Wt/WApplication jpeglib.h],
AC_CHECK_HEADERS([Wt/WApplication pstreams/pstream.h],
[],
[AC_MSG_ERROR([Header not found or unusable !])])
AC_CHECK_LIB([config++],
AC_CHECK_LIB([wt],
[main],
,
[AC_MSG_ERROR([libwt not found!])])
AC_CHECK_LIB([wtdbo],
[main],
,
[AC_MSG_ERROR([libwtdbo not found!])])
AC_CHECK_LIB([wtdbosqlite3],
[main],
,
[AC_MSG_ERROR([libwtdbosqlite3 not found!])])
AC_CHECK_LIB([wthttp],
[main],
,
[AC_MSG_ERROR([config++ not found!])])
[AC_MSG_ERROR([libwthttp not found!])])
AC_CHECK_LIB([jpeg],
[jpeg_destroy],
,
[AC_MSG_ERROR([libpthread not found!])])
AC_CHECK_LIB([pthread],
[pthread_create],
,
[AC_MSG_ERROR([libpthread not found!])])
AC_CHECK_LIB([avutil],
[av_free],
@@ -73,16 +64,6 @@ AC_CHECK_LIB([avformat],
,
[AC_MSG_ERROR([libavformat not found!])])
AC_CHECK_LIB([ssl],
[SSL_CTX_new],
,
[AC_MSG_ERROR([libssl not found!])])
AC_CHECK_LIB([crypto],
[ASN1_STRING_to_UTF8],
,
[AC_MSG_ERROR([libcrypto not found!])])
AC_CHECK_LIB([boost_system],
[main],
,
@@ -98,58 +79,16 @@ AC_CHECK_LIB( [boost_locale],
,
[AC_MSG_ERROR([libboost_locale not found!])])
AC_CHECK_LIB( [boost_thread],
[main],
,
[AC_MSG_ERROR([libboost_thread not found!])])
AC_CHECK_LIB( [boost_date_time],
[main],
,
[AC_MSG_ERROR([libboost_date_time not found!])])
#AC_CHECK_LIB( [boost_signals],
# [main],
# ,
# [AC_MSG_ERROR([libboost_signals not found!])])
AC_CHECK_LIB( [boost_iostreams],
[main],
,
[AC_MSG_ERROR([libboost_iostreams not found!])])
AC_CHECK_LIB( [boost_log],
[main],
,
[AC_MSG_ERROR([libboost_log not found!])])
AC_CHECK_LIB( [boost_log_setup],
[main],
,
[AC_MSG_ERROR([libboost_log_setup not found!])])
# TODO -> PARAMETRIZE THIS
AC_CHECK_LIB([wthttp],
[main],
,
[AC_MSG_ERROR([libwthttp not found!])])
AC_CHECK_LIB([wtdbosqlite3],
[main],
,
[AC_MSG_ERROR([libwtdbosqlite3 not found!])])
AC_CHECK_LIB([wtdbo],
[main],
,
[AC_MSG_ERROR([libwtdbo not found!])])
AC_CHECK_LIB([wt],
[main],
,
[AC_MSG_ERROR([libwt not found!])])
AC_CONFIG_FILES([Makefile
src/Makefile
test/Makefile])
@@ -14,31 +14,19 @@ div.contents {
padding: 0px 12px 6px;
}
.Wt-tableview .playqueue-playing {
color: #FFF;
}
.Wt-tableview div.playqueue-playing {
background-color: #4A42CA;
}
.Wt-tableview .Wt-tv-contents.Wt-striped div.playqueue-playing:nth-child(odd) {
background-color: #4A42CA;
}
.playqueue {
background-color: #EEE;
border-radius: 10px;
}
.playqueue-playing {
background-color: #4A42CA;
font-weight: bold;
}
.playqueue-track {
font-size: 120%;
margin-top: 12px;
line-height: normal;
font-weight: bold;
}
.playqueue-artist {
@@ -70,6 +58,7 @@ div.contents {
.mediaplayer-current-track {
font-weight: bold;
font-size: 120%;
}
.mediaplayer-current-artist {

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 = \
$(srcdir)/main/main.cpp \
$(srcdir)/av/CodecContext.cpp \
$(srcdir)/av/Common.cpp \
$(srcdir)/av/Dictionary.cpp \
$(srcdir)/av/FormatContext.cpp \
$(srcdir)/av/InputFormatContext.cpp \
$(srcdir)/av/Stream.cpp \
$(srcdir)/config/ConfigReader.cpp \
$(srcdir)/cover/CoverArt.cpp \
$(srcdir)/av/AvInfo.cpp \
$(srcdir)/av/AvTranscoder.cpp \
$(srcdir)/cover/CoverArtGrabber.cpp \
$(srcdir)/database/Artist.cpp \
$(srcdir)/database/DatabaseHandler.cpp \
@@ -23,16 +17,12 @@ lms_SOURCES = \
$(srcdir)/database/Video.cpp \
$(srcdir)/database-updater/DatabaseUpdater.cpp \
$(srcdir)/database-updater/Checksum.cpp \
$(srcdir)/image/Image.cpp \
$(srcdir)/logger/Logger.cpp \
$(srcdir)/metadata/AvFormat.cpp \
$(srcdir)/metadata/Utils.cpp \
$(srcdir)/service/ServiceManager.cpp \
$(srcdir)/service/DatabaseUpdateService.cpp \
$(srcdir)/service/UserInterfaceService.cpp \
$(srcdir)/transcode/AvConvTranscoder.cpp \
$(srcdir)/transcode/Format.cpp \
$(srcdir)/transcode/Parameters.cpp \
$(srcdir)/transcode/InputMediaFile.cpp \
$(srcdir)/ui/LmsApplication.cpp \
$(srcdir)/ui/auth/LmsAuth.cpp \
$(srcdir)/ui/audio/desktop/DesktopAudio.cpp \
@@ -51,10 +41,6 @@ lms_SOURCES = \
$(srcdir)/ui/common/LineEdit.cpp \
$(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \
$(srcdir)/ui/resource/CoverResource.cpp \
$(srcdir)/ui/video/VideoWidget.cpp \
$(srcdir)/ui/video/VideoDatabaseWidget.cpp \
$(srcdir)/ui/video/VideoMediaPlayerWidget.cpp \
$(srcdir)/ui/video/VideoParametersDialog.cpp \
$(srcdir)/ui/settings/Settings.cpp \
$(srcdir)/ui/settings/SettingsAccountFormView.cpp \
$(srcdir)/ui/settings/SettingsAudioFormView.cpp \
@@ -65,51 +51,14 @@ lms_SOURCES = \
$(srcdir)/ui/settings/SettingsUserFormView.cpp \
$(srcdir)/ui/settings/SettingsUsers.cpp
if LMSAPI
if VIDEO
lms_SOURCES += \
$(srcdir)/lms-api/server/Connection.cpp \
$(srcdir)/lms-api/server/ConnectionManager.cpp \
$(srcdir)/lms-api/server/AudioCollectionRequestHandler.cpp \
$(srcdir)/lms-api/server/AuthRequestHandler.cpp \
$(srcdir)/lms-api/server/MediaRequestHandler.cpp \
$(srcdir)/lms-api/server/RequestHandler.cpp \
$(srcdir)/lms-api/server/Server.cpp \
$(srcdir)/service/LmsAPIServerService.cpp
nodist_lms_SOURCES = \
$(builddir)/auth.pb.cc \
$(builddir)/auth.pb.h \
$(builddir)/collection.pb.cc \
$(builddir)/collection.pb.h \
$(builddir)/media.pb.cc \
$(builddir)/media.pb.h \
$(builddir)/messages.pb.cc \
$(builddir)/messages.pb.h
BUILT_SOURCES = \
$(builddir)/auth.pb.cc \
$(builddir)/auth.pb.h \
$(builddir)/collection.pb.cc \
$(builddir)/collection.pb.h \
$(builddir)/media.pb.cc \
$(builddir)/media.pb.h \
$(builddir)/messages.pb.cc \
$(builddir)/messages.pb.h
MOSTLYCLEANFILES = \
$(builddir)/auth.pb.cc \
$(builddir)/auth.pb.h \
$(builddir)/collection.pb.cc \
$(builddir)/collection.pb.h \
$(builddir)/media.pb.cc \
$(builddir)/media.pb.h \
$(builddir)/messages.pb.cc \
$(builddir)/messages.pb.h
%.pb.cc %.pb.h: $(top_srcdir)/lms-api/proto/%.proto
$(PROTOC) --proto_path=$(top_srcdir)/lms-api/proto/ --cpp_out=$(builddir)/ $^
$(srcdir)/ui/video/VideoWidget.cpp \
$(srcdir)/ui/video/VideoDatabaseWidget.cpp \
$(srcdir)/ui/video/VideoMediaPlayerWidget.cpp \
$(srcdir)/ui/video/VideoParametersDialog.cpp
endif
lms_CXXFLAGS=-DBOOST_LOG_DYN_LINK -std=c++11 -Wall -I$(top_srcdir)/third-party -I$(srcdir)/ui -I$(srcdir)/lms-api
lms_CXXFLAGS=-std=c++11 -Wall -I$(top_srcdir)/third-party -I$(srcdir)/ui $(MAGICKXX_CFLAGS)
lms_LDADD=$(MAGICKXX_LIBS)
+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() << "': " << 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 on '" << _p.string() << "': " << 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
+330
View File
@@ -0,0 +1,330 @@
/*
* 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 <atomic>
#include <mutex>
#include <boost/tokenizer.hpp>
#include "logger/Logger.hpp"
#include "AvTranscoder.hpp"
namespace Av {
#define LMS_LOG_TRANSCODE(sev) LMS_LOG(TRANSCODE, INFO) << "[" << _id << "] - "
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?
static const std::vector<std::string> execNames =
{
"avconv",
"ffmpeg",
};
static std::mutex transcoderMutex;
static boost::filesystem::path avConvPath = boost::filesystem::path();
static std::atomic<size_t> globalId = {0};
static std::string searchPath(std::string filename)
{
std::string path;
path = ::getenv("PATH");
if (path.empty())
throw std::runtime_error("Environment variable PATH not found");
std::string result;
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
boost::char_separator<char> sep(":");
tokenizer tok(path, sep);
for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
{
boost::filesystem::path p = *it;
p /= filename;
if (!::access(p.c_str(), X_OK))
{
result = p.string();
break;
}
}
return result;
}
void
Transcoder::init()
{
for (std::string execName : execNames)
{
boost::filesystem::path p = searchPath(execName);
if (!p.empty())
{
avConvPath = p;
break;
}
}
if (!avConvPath.empty())
LMS_LOG(TRANSCODE, INFO) << "Using transcoder " << avConvPath;
else
throw std::runtime_error("Cannot find any transcoder binary!");
}
Transcoder::Transcoder(boost::filesystem::path filePath, TranscodeParameters parameters)
: _filePath(filePath),
_parameters(parameters),
_isComplete(false),
_id(globalId++)
{
}
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 << "'";
std::vector<std::string> args;
args.push_back(avConvPath.string());
// Make sure we do not produce anything in the stderr output
// in order not to block the whole forked process
args.push_back("-loglevel");
args.push_back("quiet");
// input Offset
if (_parameters.getOffset().total_seconds() > 0)
{
args.push_back("-ss");
args.push_back(std::to_string(_parameters.getOffset().total_seconds()));
}
// Input file
args.push_back("-i");
args.push_back(_filePath.string());
// Output bitrates
args.push_back("-b:a");
args.push_back(std::to_string(_parameters.getBitrate(Stream::Type::Audio)));
// if (_parameters.getOutputFormat().getType() == Format::Video)
// oss << " -b:v " << _parameters.getOutputBitrate(Stream::Video);
// Stream mapping
for (int streamId : _parameters.getSelectedStreamIds())
{
// 0 means the first input file
args.push_back("-map");
args.push_back("0:" + std::to_string(streamId));
}
// Codecs and formats
switch( _parameters.getEncoding())
{
case Encoding::MP3:
args.push_back("-f");
args.push_back("mp3");
break;
case Encoding::OGA:
args.push_back("-acodec");
args.push_back("libvorbis");
args.push_back("-f");
args.push_back("ogg");
break;
case Encoding::OGV:
args.push_back("-acodec");
args.push_back("libvorbis");
args.push_back("-ac");
args.push_back("2");
args.push_back("-ar");
args.push_back("44100");
args.push_back("-vcodec");
args.push_back("libtheora");
args.push_back("-threads");
args.push_back("4");
args.push_back("-f");
args.push_back("ogg");
break;
case Encoding::WEBMA:
args.push_back("-codec:a");
args.push_back("libvorbis");
args.push_back("-f");
args.push_back("webm");
break;
case Encoding::WEBMV:
args.push_back("-acodec");
args.push_back("libvorbis");
args.push_back("-ac");
args.push_back("2");
args.push_back("-ar");
args.push_back("44100");
args.push_back("-vcodec");
args.push_back("libvpx");
args.push_back("-threads");
args.push_back("4");
args.push_back("-f");
args.push_back("webm");
break;
case Encoding::M4A:
args.push_back("-acodec");
args.push_back("aac");
args.push_back("-f");
args.push_back("mp4");
args.push_back("-strict");
args.push_back("experimental");
break;
case Encoding::M4V:
args.push_back("-acodec");
args.push_back("aac");
args.push_back("-strict");
args.push_back("experimental");
args.push_back("-ac");
args.push_back("2");
args.push_back("-ar");
args.push_back("-44100");
args.push_back("-vcodec");
args.push_back("libx264");
args.push_back("-f");
args.push_back("m4v");
break;
case Encoding::FLV:
args.push_back("-acodec");
args.push_back("libmp3lame");
args.push_back("-ac");
args.push_back("2");
args.push_back("-ar");
args.push_back("44100");
args.push_back("-vcodec");
args.push_back("libx264");
args.push_back("-f");
args.push_back("flv");
break;
case Encoding::FLA:
args.push_back("-acodec");
args.push_back("libmp3lame");
args.push_back("-f");
args.push_back("flv");
break;
default:
return false;
}
args.push_back("pipe:1");
LMS_LOG_TRANSCODE(INFO) << "Dumping args (" << args.size() << ")";
for (std::string arg : args)
LMS_LOG_TRANSCODE(DEBUG) << "Arg = '" << arg << "'";
// make sure only one thread is executing this part of code
{
std::lock_guard<std::mutex> lock(transcoderMutex);
_child = std::make_shared<redi::ipstream>();
const redi::pstreams::pmode mode = redi::pstreams::pstdout; // | redi::pstreams::pstderr;
_child->open(avConvPath.string(), args, mode);
if (!_child->is_open())
{
LMS_LOG_TRANSCODE(DEBUG) << "Exec failed!";
return false;
}
if (_child->out().eof())
{
LMS_LOG_TRANSCODE(DEBUG) << "Early end of file!";
return false;
}
LMS_LOG_TRANSCODE(DEBUG) << "Stream opened!";
}
return true;
}
void
Transcoder::process(std::vector<unsigned char>& output, std::size_t maxSize)
{
if (!_child || _isComplete)
return;
output.resize(maxSize);
//Read on the output stream
_child->out().read(reinterpret_cast<char*>(&output[0]), maxSize);
output.resize(_child->out().gcount());
LMS_LOG_TRANSCODE(DEBUG) << "Read " << output.size() << " bytes";
if (_child->out().eof())
{
LMS_LOG_TRANSCODE(DEBUG) << "Stdout EOF!";
_child->clear();
_isComplete = true;
_child.reset();
}
_total += output.size();
LMS_LOG_TRANSCODE(DEBUG) << "nb bytes = " << output.size() << ", total = " << _total;
}
Transcoder::~Transcoder()
{
LMS_LOG_TRANSCODE(DEBUG) << ", ~Transcoder called! Total produced bytes = " << _total;
if (_child)
{
LMS_LOG_TRANSCODE(DEBUG) << "Child still here!";
_child->rdbuf()->kill(SIGKILL);
LMS_LOG_TRANSCODE(DEBUG) << "Closing...";
_child->rdbuf()->close();
LMS_LOG_TRANSCODE(DEBUG) << "Closing DONE";
}
}
} // namespace Transcode
+108
View File
@@ -0,0 +1,108 @@
/*
* 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 "pstreams/pstream.h"
#include <boost/date_time/posix_time/posix_time_types.hpp> //no i/o just types
#include <boost/filesystem/path.hpp>
#include <boost/filesystem.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) { return _isComplete; }
private:
Transcoder();
boost::filesystem::path _filePath;
TranscodeParameters _parameters;
std::shared_ptr<redi::ipstream> _child;
bool _isComplete = false;
std::size_t _total = 0;
std::size_t _id;
};
} // 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
-52
View File
@@ -1,52 +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 FORMAT_CONTEXT_HPP
#define FORMAT_CONTEXT_HPP
#include <boost/filesystem.hpp>
#include "Common.hpp"
namespace Av
{
class FormatContext
{
public:
FormatContext();
~FormatContext();
protected:
void native(AVFormatContext* c) { _context = c;}
AVFormatContext* native() { return _context; }
const AVFormatContext* native() const { return _context; }
private:
AVFormatContext* _context;
};
} // namespace Av
#endif
-173
View File
@@ -1,173 +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;
}
}
void
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;
throw std::runtime_error("av_find_stream_info failed!");
}
}
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
void 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
View File
-74
View File
@@ -1,74 +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)
{
return _config->lookup(setting);
}
unsigned long
ConfigReader::getULong(std::string setting)
{
return static_cast<unsigned int>(_config->lookup(setting));
}
long
ConfigReader::getLong(std::string setting)
{
return _config->lookup(setting);
}
bool
ConfigReader::getBool(std::string setting)
{
return _config->lookup(setting);
}
-49
View File
@@ -1,49 +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);
std::string getString(std::string setting);
unsigned long getULong(std::string setting);
long getLong(std::string setting);
bool getBool(std::string setting);
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 "config/ConfigReader.hpp"
#include "av/InputFormatContext.hpp"
#include "av/AvInfo.hpp"
#include "CoverArtGrabber.hpp"
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
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 {
Grabber::Grabber()
: _maxFileSize(0)
{
}
@@ -67,40 +52,28 @@ Grabber::instance()
return instance;
}
void
Grabber::init()
static std::vector<Image::Image>
getFromAvMediaFile(const Av::MediaFile& input, std::size_t nbMaxCovers)
{
for (const std::string& extension : splitStrings( ConfigReader::instance().getString("main.cover.file_extensions")))
_fileExtensions.push_back("." + extension);
std::vector<Image::Image> res;
_maxFileSize = ConfigReader::instance().getULong("main.cover.file_max_size");
}
std::vector<CoverArt>
Grabber::getFromInputFormatContext(const Av::InputFormatContext& input, std::size_t nbMaxCovers) const
{
std::vector<CoverArt> res;
try
for (Av::Picture& picture : input.getAttachedPictures(nbMaxCovers))
{
std::vector<Av::Picture> pictures = input.getPictures(nbMaxCovers);
Image::Image image;
for (Av::Picture& picture : pictures)
res.push_back( CoverArt(picture.mimeType, picture.data) );
}
catch(std::exception& e)
{
LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get pictures: " << e.what();
if (image.load(picture.data))
res.push_back( image );
else
LMS_LOG(COVER, ERROR) << "Cannot load embedded cover file in '" << input.getPath() << "'";
}
return res;
}
std::vector<CoverArt>
std::vector<Image::Image>
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);
for (auto coverPath : coverPathes)
@@ -108,14 +81,12 @@ Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCov
if (res.size() >= nbMaxCovers)
break;
std::vector<unsigned char> data;
std::ifstream file(coverPath.string(), std::ios::binary);
char c;
while (file.get(c))
data.push_back(c);
Image::Image image;
// TODO handle other formats
res.push_back(CoverArt("image/jpeg", data));
if (image.load(coverPath))
res.push_back(image);
else
LMS_LOG(COVER, ERROR) << "Cannot load image in file '" << coverPath << "'";
}
return res;
@@ -143,7 +114,7 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t
if (boost::filesystem::file_size(path) > _maxFileSize)
{
LMS_LOG(MOD_COVER, SEV_INFO) << "Cover file '" << path << " is too big (" << boost::filesystem::file_size(path) << "), limit is " << _maxFileSize;
LMS_LOG(COVER, INFO) << "Cover file '" << path << " is too big (" << boost::filesystem::file_size(path) << "), limit is " << _maxFileSize;
continue;
}
@@ -155,27 +126,18 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t
return res;
}
std::vector<CoverArt>
std::vector<Image::Image>
Grabber::getFromTrack(const boost::filesystem::path& p, std::size_t nbMaxCovers) const
{
std::vector<CoverArt> res;
Av::MediaFile input(p);
try
{
Av::InputFormatContext input(p);
res = getFromInputFormatContext(input, nbMaxCovers);
}
catch(std::exception& e)
{
LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get covers from file " << p << ": " << e.what();
}
return res;
if (input.open())
return getFromAvMediaFile(input, nbMaxCovers);
else
return std::vector<Image::Image>();
}
std::vector<CoverArt>
std::vector<Image::Image>
Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers) const
{
using namespace Database;
@@ -184,7 +146,7 @@ Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackI
Track::pointer track = Track::getById(session, trackId);
if (!track)
return std::vector<CoverArt>();
return std::vector<Image::Image>();
Track::CoverType coverType = track->getCoverType();
boost::filesystem::path trackPath = track->getPath();
@@ -195,17 +157,15 @@ Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackI
{
case Track::CoverType::Embedded:
return Grabber::getFromTrack(trackPath, nbMaxCovers);
case Track::CoverType::ExternalFile:
return Grabber::getFromDirectory(trackPath.parent_path(), nbMaxCovers);
case Track::CoverType::None:
return std::vector<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
{
using namespace Database;
@@ -222,14 +182,14 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type re
-1, 1 /* limit result size */);
if (tracks.empty())
return std::vector<CoverArt>();
return std::vector<Image::Image>();
firstTrackPath = tracks.front()->getPath();
embeddedCover = (tracks.front()->getCoverType() == Track::CoverType::Embedded);
}
// 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
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.
*
@@ -17,16 +17,13 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef COVER_ART_GRABBER_HPP
#define COVER_ART_GRABBER_HPP
#pragma once
#include <vector>
#include "av/InputFormatContext.hpp"
#include "database/Types.hpp"
#include "CoverArt.hpp"
#include "image/Image.hpp"
namespace CoverArt {
@@ -38,26 +35,21 @@ class Grabber
static Grabber& instance();
void init();
std::vector<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<CoverArt> getFromInputFormatContext(const Av::InputFormatContext& input, std::size_t nbMaxCovers = 1) const;
std::vector<CoverArt> getFromTrack(const boost::filesystem::path& path, 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<CoverArt> getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers = 1) const;
std::vector<Image::Image> getFromDirectory(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const;
std::vector<Image::Image> 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<Image::Image> getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers = 1) const;
private:
Grabber();
std::vector<boost::filesystem::path> _fileExtensions;
std::size_t _maxFileSize;
std::vector<boost::filesystem::path> _preferredFileNames;
std::vector<boost::filesystem::path> _fileExtensions
= {".jpg", ".jpeg", ".png", ".bmp"}; // TODO parametrize
std::size_t _maxFileSize = 5000000;
std::vector<boost::filesystem::path> _preferredFileNames
= {"cover", "front"}; // TODO parametrize
};
} // namespace CoverArt
#endif
+1 -1
View File
@@ -47,7 +47,7 @@ void computeCrc(const boost::filesystem::path& p, std::vector<unsigned char>& cr
}
else
{
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Failed to open file '" << p << "'";
LMS_LOG(DBUPDATER, ERROR) << "Failed to open file '" << p << "'";
throw std::runtime_error("Failed to open file '" + p.string() + "'" );
}
+206 -172
View File
@@ -19,7 +19,6 @@
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/thread.hpp>
#include <boost/asio/placeholders.hpp>
@@ -73,7 +72,7 @@ isFileSupported(const boost::filesystem::path& file, const std::vector<boost::fi
boost::filesystem::path fileExtension = file.extension();
BOOST_FOREACH(const boost::filesystem::path extension, extensions)
for (auto& extension : extensions)
{
if (extension == fileExtension)
return true;
@@ -85,15 +84,33 @@ isFileSupported(const boost::filesystem::path& file, const std::vector<boost::fi
std::vector<boost::filesystem::path>
getRootDirectoriesByType(Wt::Dbo::Session& session, Database::MediaDirectory::Type type)
{
Wt::Dbo::Transaction transaction(session);
std::vector<boost::filesystem::path> res;
std::vector<Database::MediaDirectory::pointer> rootDirs = Database::MediaDirectory::getByType(session, type);
BOOST_FOREACH(Database::MediaDirectory::pointer rootDir, rootDirs)
for (auto rootDir : rootDirs)
res.push_back(rootDir->getPath());
return res;
}
bool
isPathInParentPath(const boost::filesystem::path& path, const boost::filesystem::path& parentPath)
{
boost::filesystem::path curPath = path;
while (curPath.has_parent_path())
{
curPath = curPath.parent_path();
if (curPath == parentPath)
return true;
}
return false;
}
} // namespace
@@ -102,29 +119,15 @@ namespace DatabaseUpdater {
using namespace Database;
Updater::Updater(boost::filesystem::path dbPath, MetaData::Parser& parser)
Updater::Updater(Wt::Dbo::SqlConnectionPool &connectionPool, MetaData::Parser& parser)
: _running(false),
_scheduleTimer(_ioService),
_db(dbPath),
_db(connectionPool),
_metadataParser(parser)
{
_ioService.setThreadCount(1);
}
void
Updater::setAudioExtensions(const std::vector<std::string>& extensions)
{
BOOST_FOREACH(const std::string& extension, extensions)
_audioExtensions.push_back("." + extension);
}
void
Updater::setVideoExtensions(const std::vector<std::string>& extensions)
{
BOOST_FOREACH(const std::string& extension, extensions)
_videoExtensions.push_back("." + extension);
}
void
Updater::start(void)
{
@@ -155,7 +158,7 @@ Updater::processNextJob(void)
MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(_db.getSession());
if (settings->getManualScanRequested()) {
LMS_LOG(MOD_DBUPDATER, SEV_NOTICE) << "Manual scan requested!";
LMS_LOG(DBUPDATER, INFO) << "Manual scan requested!";
scheduleScan( boost::posix_time::seconds(0) );
}
else
@@ -198,7 +201,7 @@ Updater::processNextJob(void)
void
Updater::scheduleScan( boost::posix_time::time_duration duration)
{
LMS_LOG(MOD_DBUPDATER, SEV_NOTICE) << "Scheduling next scan in " << duration;
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan in " << duration;
_scheduleTimer.expires_from_now(duration);
_scheduleTimer.async_wait( boost::bind( &Updater::process, this, boost::asio::placeholders::error) );
}
@@ -206,7 +209,7 @@ Updater::scheduleScan( boost::posix_time::time_duration duration)
void
Updater::scheduleScan( boost::posix_time::ptime time)
{
LMS_LOG(MOD_DBUPDATER, SEV_NOTICE) << "Scheduling next scan at " << time;
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan at " << time;
_scheduleTimer.expires_at(time);
_scheduleTimer.async_wait( boost::bind( &Updater::process, this, boost::asio::placeholders::error) );
}
@@ -216,24 +219,32 @@ Updater::process(boost::system::error_code err)
{
if (!err)
{
updateFileExtensions();
Stats stats;
checkAudioFiles(stats);
checkVideoFiles(stats);
typedef std::pair<boost::filesystem::path, Database::MediaDirectory::Type> RootDirectory;
std::vector<RootDirectory> rootDirectories;
{
Wt::Dbo::Transaction transaction(_db.getSession());
std::vector<MediaDirectory::pointer> mediaDirectories = MediaDirectory::getAll(_db.getSession());
BOOST_FOREACH(MediaDirectory::pointer directory, mediaDirectories)
rootDirectories.push_back( std::make_pair( directory->getPath(), directory->getType() ));
for (MediaDirectory::pointer directory : MediaDirectory::getAll(_db.getSession()))
rootDirectories.push_back( RootDirectory( directory->getType(), directory->getPath() ));
}
for (RootDirectory rootDirectory : rootDirectories)
processDirectory(rootDirectory.first, rootDirectory.first, rootDirectory.second, stats);
{
if (!_running)
break;
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Changes = " << stats.nbChanges();
LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory.path << "'...";
processRootDirectory(rootDirectory, stats);
LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory.path << "' DONE";
}
LMS_LOG(DBUPDATER, INFO) << "Scan complete. Changes = " << stats.nbChanges() << ", Errors = " << stats.nbScanErrors;
// Update database stats
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
@@ -261,6 +272,15 @@ Updater::process(boost::system::error_code err)
}
}
void
Updater::updateFileExtensions()
{
Wt::Dbo::Transaction transaction(_db.getSession());
_audioFileExtensions = MediaDirectorySettings::get(_db.getSession())->getAudioFileExtensions();
_videoFileExtensions = MediaDirectorySettings::get(_db.getSession())->getVideoFileExtensions();
}
Artist::pointer
Updater::getArtist( const boost::filesystem::path& file, const std::string& name, const std::string& mbid)
{
@@ -358,65 +378,59 @@ Updater::getGenres( const std::list<std::string>& names)
void
Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
{
try {
// Check last update time
boost::posix_time::ptime lastWriteTime (boost::posix_time::from_time_t( boost::filesystem::last_write_time( file ) ) );
boost::posix_time::ptime lastWriteTime (boost::posix_time::from_time_t( boost::filesystem::last_write_time( file ) ) );
// Skip file if last write is the same
{
Wt::Dbo::Transaction transaction(_db.getSession());
// Skip file if last write is the same
Wt::Dbo::ptr<Track> track = Track::getByPath(_db.getSession(), file);
// if the file is the same and embeds covers, no need to update
if (track && track->getLastWriteTime() == lastWriteTime
&& (track->getCoverType() == Database::Track::CoverType::Embedded))
return;
// Check for external covers
std::vector<boost::filesystem::path> externalCovers = CoverArt::Grabber::instance().getCoverPaths(file.parent_path());
if (track && track->getLastWriteTime() == lastWriteTime)
{
// no change since last time we updated
// Skip only if no external covers has to be set
if (((track->getCoverType() == Database::Track::CoverType::None) && externalCovers.empty())
|| ((track->getCoverType() == Database::Track::CoverType::ExternalFile) && !externalCovers.empty()))
return;
}
}
MetaData::Items items;
_metadataParser.parse(file, items);
MetaData::Items items;
if (!_metadataParser.parse(file, items))
return;
// We estimate this is a audio file if:
// - we found a least one audio stream
// - the duration is not null
if (items.find(MetaData::Type::AudioStreams) == items.end()
|| boost::any_cast<std::vector<MetaData::AudioStream> >(items[MetaData::Type::AudioStreams]).empty())
Wt::Dbo::Transaction transaction(_db.getSession());
Wt::Dbo::ptr<Track> track = Track::getByPath(_db.getSession(), file);
// We estimate this is a audio file if:
// - we found a least one audio stream
// - the duration is not null
if (items.find(MetaData::Type::AudioStreams) == items.end()
|| boost::any_cast<std::vector<MetaData::AudioStream> >(items[MetaData::Type::AudioStreams]).empty())
{
LMS_LOG(DBUPDATER, DEBUG) << "Skipped '" << file << "' (no audio stream found)";
// If Track exists here, delete it!
if (track)
{
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Skipped '" << file << "' (no audio stream found)";
// If Track exists here, delete it!
if (track) {
track.remove();
stats.nbRemoved++;
}
return;
track.remove();
stats.nbRemoved++;
}
if (items.find(MetaData::Type::Duration) == items.end()
|| boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]).total_seconds() <= 0)
return;
}
if (items.find(MetaData::Type::Duration) == items.end()
|| boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Type::Duration]).total_seconds() <= 0)
{
LMS_LOG(DBUPDATER, DEBUG) << "Skipped '" << file << "' (no duration or duration <= 0)";
// If Track exists here, delete it!
if (track)
{
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Skipped '" << file << "' (no duration or duration <= 0)";
// If Track exists here, delete it!
if (track) {
track.remove();
stats.nbRemoved++;
}
return;
track.remove();
stats.nbRemoved++;
}
return;
}
// ***** Title
std::string title;
if (items.find(MetaData::Type::Title) != items.end()) {
// ***** Title
std::string title;
if (items.find(MetaData::Type::Title) != items.end()) {
title = boost::any_cast<std::string>(items[MetaData::Type::Title]);
}
else
@@ -477,12 +491,12 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
{
// Create a new song
track = Track::create(_db.getSession(), file);
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Adding '" << file << "'";
LMS_LOG(DBUPDATER, INFO) << "Adding '" << file << "'";
stats.nbAdded++;
}
else
{
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Updating '" << file << "'";
LMS_LOG(DBUPDATER, INFO) << "Updating '" << file << "'";
stats.nbModified++;
}
@@ -531,52 +545,39 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
{
bool hasCover = boost::any_cast<bool>(items[MetaData::Type::HasCover]);
if (hasCover)
track.modify()->setCoverType( Track::CoverType::Embedded );
else if (!externalCovers.empty())
track.modify()->setCoverType( Track::CoverType::ExternalFile );
else
track.modify()->setCoverType( Track::CoverType::None);
track.modify()->setCoverType( hasCover ? Track::CoverType::Embedded : Track::CoverType::None );
}
transaction.commit();
}
catch( std::exception& e ) {
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Exception while parsing audio file : '" << file << "': '" << e.what() << "' => skipping!";
}
}
void
Updater::processDirectory(const boost::filesystem::path& rootDirectory,
const boost::filesystem::path& p,
Database::MediaDirectory::Type type,
Stats& stats)
Updater::processRootDirectory(RootDirectory rootDirectory, Stats& stats)
{
if (!_running)
if (!boost::filesystem::exists(rootDirectory.path) || !boost::filesystem::is_directory(rootDirectory.path))
return;
if (!boost::filesystem::exists(p) || !boost::filesystem::is_directory(p))
return;
boost::filesystem::recursive_directory_iterator itPath(rootDirectory);
boost::filesystem::recursive_directory_iterator itPath(rootDirectory.path);
boost::filesystem::recursive_directory_iterator itEnd;
while (itPath != itEnd)
{
if (!_running)
return;
if (boost::filesystem::is_regular(*itPath)) {
switch( type )
if (boost::filesystem::is_regular(*itPath))
{
switch( rootDirectory.type )
{
case Database::MediaDirectory::Audio:
if (isFileSupported(*itPath, _audioExtensions))
if (isFileSupported(*itPath, _audioFileExtensions))
processAudioFile( *itPath, stats );
break;
case Database::MediaDirectory::Video:
if (isFileSupported(*itPath, _videoExtensions))
if (isFileSupported(*itPath, _videoFileExtensions))
processVideoFile( *itPath, stats);
break;
}
@@ -589,119 +590,150 @@ Updater::processDirectory(const boost::filesystem::path& rootDirectory,
bool
Updater::checkFile(const boost::filesystem::path& p, const std::vector<boost::filesystem::path>& rootDirs, const std::vector<boost::filesystem::path>& extensions)
{
bool status = true;
try
{
bool status = true;
// For each track, make sure the the file still exists
// and still belongs to a root directory
if (!boost::filesystem::exists( p )
|| !boost::filesystem::is_regular( p ) )
{
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Missing file '" << p << "'";
status = false;
}
else
{
bool foundRoot = false;
BOOST_FOREACH(const boost::filesystem::path& rootDir, rootDirs)
// For each track, make sure the the file still exists
// and still belongs to a root directory
if (!boost::filesystem::exists( p )
|| !boost::filesystem::is_regular( p ) )
{
if (p.string().find( rootDir.string() ) != std::string::npos)
LMS_LOG(DBUPDATER, INFO) << "Missing file '" << p << "'";
status = false;
}
else
{
bool foundRoot = false;
for (auto& rootDir : rootDirs)
{
foundRoot = true;
break;
if (isPathInParentPath(p, rootDir))
{
foundRoot = true;
break;
}
}
if (!foundRoot)
{
LMS_LOG(DBUPDATER, INFO) << "Out of root file '" << p << "'";
status = false;
}
else if (!isFileSupported(p, extensions))
{
LMS_LOG(DBUPDATER, INFO) << "File format no longer supported for '" << p << "'";
status = false;
}
}
if (!foundRoot)
{
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Out of root file '" << p << "'";
status = false;
}
else if (!isFileSupported(p, extensions))
{
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "File format no longer supported for '" << p << "'";
status = false;
}
return status;
}
catch (boost::filesystem::filesystem_error& e)
{
LMS_LOG(DBUPDATER, ERROR) << "Caught exception while checking file '" << p << "': " << e.what();
return false;
}
return status;
}
void
Updater::checkAudioFiles( Stats& stats )
{
LMS_LOG(DBUPDATER, INFO) << "Checking audio files...";
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Checking audio files...";
Wt::Dbo::Transaction transaction(_db.getSession());
std::vector<boost::filesystem::path> trackPaths = Track::getAllPaths(_db.getSession());;
std::vector<boost::filesystem::path> rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Audio);
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking tracks...";
auto tracks = Track::getAll(_db.getSession());
for (auto track : tracks)
LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks...";
for (auto& trackPath : trackPaths)
{
if (!checkFile(track->getPath(), rootDirs, _audioExtensions))
if (!_running)
return;
if (!checkFile(trackPath, rootDirs, _audioFileExtensions))
{
track.remove();
stats.nbRemoved++;
Wt::Dbo::Transaction transaction(_db.getSession());
Track::pointer track = Track::getByPath(_db.getSession(), trackPath);
if (track)
{
track.remove();
stats.nbRemoved++;
}
}
}
// Now process orphan Genre (no track)
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Genres...";
auto genres = Genre::getAll(_db.getSession());
for (auto genre : genres)
LMS_LOG(DBUPDATER, DEBUG) << "Checking Genres...";
{
if (genre->getTracks().size() == 0)
Wt::Dbo::Transaction transaction(_db.getSession());
// Now process orphan Genre (no track)
auto genres = Genre::getAll(_db.getSession());
for (auto genre : genres)
{
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Removing orphan genre '" << genre->getName() << "'";
genre.remove();
if (genre->getTracks().size() == 0)
{
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan genre '" << genre->getName() << "'";
genre.remove();
}
}
}
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking artists...";
auto artists = Artist::getAllOrphans(_db.getSession());
for (auto artist : artists)
LMS_LOG(DBUPDATER, DEBUG) << "Checking artists...";
{
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Removing orphan artist '" << artist->getName() << "'";
artist.remove();
Wt::Dbo::Transaction transaction(_db.getSession());
auto artists = Artist::getAllOrphans(_db.getSession());
for (auto artist : artists)
{
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan artist '" << artist->getName() << "'";
artist.remove();
}
}
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking releases...";
auto releases = Release::getAllOrphans(_db.getSession());
for (auto release : releases)
LMS_LOG(DBUPDATER, DEBUG) << "Checking releases...";
{
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Removing orphan release '" << release->getName() << "'";
release.remove();
Wt::Dbo::Transaction transaction(_db.getSession());
auto releases = Release::getAllOrphans(_db.getSession());
for (auto release : releases)
{
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan release '" << release->getName() << "'";
release.remove();
}
}
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Check audio files done!";
LMS_LOG(DBUPDATER, INFO) << "Check audio files done!";
}
void
Updater::checkVideoFiles( Stats& stats )
{
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking video files...";
Wt::Dbo::Transaction transaction(_db.getSession());
std::vector<boost::filesystem::path> rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Video);
std::vector<boost::filesystem::path> videoPaths = Video::getAllPaths(_db.getSession());
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking videos...";
typedef Wt::Dbo::collection< Wt::Dbo::ptr<Video> > Videos;
Videos videos = Video::getAll(_db.getSession());
for (Videos::iterator it = videos.begin(); it != videos.end(); ++it)
LMS_LOG(DBUPDATER, DEBUG) << "Checking videos...";
for (auto& videoPath : videoPaths)
{
Video::pointer video = (*it);
if (!_running)
return;
if (!checkFile(video->getPath(), rootDirs, _videoExtensions))
if (!checkFile(videoPath, rootDirs, _videoFileExtensions))
{
video.remove();
stats.nbRemoved++;
Wt::Dbo::Transaction transaction(_db.getSession());
Video::pointer video = Video::getByPath(_db.getSession(), videoPath);
if (video)
{
video.remove();
stats.nbRemoved++;
}
}
}
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Check video files done!";
LMS_LOG(DBUPDATER, DEBUG) << "Check video files done!";
}
void
Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
@@ -727,7 +759,7 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
if (items.find(MetaData::Type::VideoStreams) == items.end()
|| 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 (video) {
@@ -739,7 +771,7 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
if (items.find(MetaData::Type::Duration) == items.end()
|| 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 (video) {
@@ -755,12 +787,12 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
if (!video)
{
video = Video::create(_db.getSession(), file);
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Adding '" << file << "'";
LMS_LOG(DBUPDATER, DEBUG) << "Adding '" << file << "'";
stats.nbAdded++;
}
else
{
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Updating '" << file << "'";
LMS_LOG(DBUPDATER, DEBUG) << "Updating '" << file << "'";
stats.nbModified++;
}
@@ -772,8 +804,10 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
transaction.commit();
}
catch( std::exception& e ) {
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Exception while parsing video file : '" << file << "': '" << e.what() << "' => skipping!";
catch( std::exception& e )
{
LMS_LOG(DBUPDATER, ERROR) << "Exception while parsing video file : '" << file << "': '" << e.what() << "' => skipping!";
stats.nbScanErrors++;
}
}
+17 -12
View File
@@ -32,7 +32,7 @@ namespace DatabaseUpdater {
class Updater
{
public:
Updater(boost::filesystem::path db, MetaData::Parser& parser);
Updater(Wt::Dbo::SqlConnectionPool& connectionPool, MetaData::Parser& parser);
void setAudioExtensions(const std::vector<std::string>& extensions);
void setVideoExtensions(const std::vector<std::string>& extensions);
@@ -44,15 +44,22 @@ class Updater
struct Stats
{
std::size_t nbAdded;
std::size_t nbRemoved;
std::size_t nbModified;
Stats() : nbAdded(0), nbRemoved(0), nbModified(0) {}
std::size_t nbAdded = 0;
std::size_t nbRemoved = 0;
std::size_t nbModified = 0;
std::size_t nbScanErrors = 0;
void clear(void) { nbAdded = 0; nbRemoved = 0; nbModified = 0; }
std::size_t nbChanges() const { return nbAdded + nbRemoved + nbModified;}
};
struct RootDirectory
{
Database::MediaDirectory::Type type;
boost::filesystem::path path;
RootDirectory(Database::MediaDirectory::Type t, boost::filesystem::path p) : type(t), path(p) {}
};
// Job handling
void processNextJob();
void scheduleScan(boost::posix_time::time_duration duration);
@@ -67,15 +74,13 @@ class Updater
const std::vector<boost::filesystem::path>& extensions);
void processDirectory( const boost::filesystem::path& rootDirectory,
const boost::filesystem::path& directory,
Database::MediaDirectory::Type type,
Stats& stats);
void processRootDirectory( RootDirectory rootDirectory, Stats& stats);
// Helpers
Database::Artist::pointer getArtist( const boost::filesystem::path& file, const std::string& name, const std::string& MBID);
Database::Release::pointer getRelease( const boost::filesystem::path& file, const std::string& name, const std::string& MBID);
std::vector<Database::Genre::pointer> getGenres( const std::list<std::string>& names);
void updateFileExtensions();
// Audio
void checkAudioFiles( Stats& stats );
@@ -92,8 +97,8 @@ class Updater
Database::Handler _db;
std::vector<boost::filesystem::path> _audioExtensions;
std::vector<boost::filesystem::path> _videoExtensions;
std::vector<boost::filesystem::path> _audioFileExtensions;
std::vector<boost::filesystem::path> _videoFileExtensions;
MetaData::Parser& _metadataParser;
+33 -11
View File
@@ -17,6 +17,9 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/Dbo/FixedSqlConnectionPool>
#include <Wt/Dbo/backend/Sqlite3>
#include <Wt/Auth/Dbo/AuthInfo>
#include <Wt/Auth/Dbo/UserDatabase>
#include <Wt/Auth/AuthService>
@@ -72,11 +75,11 @@ Handler::getPasswordService()
}
Handler::Handler(boost::filesystem::path db)
:
_dbBackend( db.string() )
Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
{
_session.setConnection(_dbBackend);
_session.setConnectionPool(connectionPool);
_session.mapClass<Database::Artist>("artist");
_session.mapClass<Database::Genre>("genre");
_session.mapClass<Database::Track>("track");
@@ -93,17 +96,23 @@ _dbBackend( db.string() )
_session.mapClass<Database::AuthInfo::AuthTokenType>("auth_token");
try {
Wt::Dbo::Transaction transaction(_session);
_session.createTables();
_dbBackend.executeSql("CREATE INDEX artist_name_idx ON artist(name)");
_dbBackend.executeSql("CREATE INDEX genre_name_idx ON genre(name)");
_dbBackend.executeSql("CREATE INDEX release_name_idx ON release(name)");
_dbBackend.executeSql("CREATE INDEX track_name_idx ON track(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 release_name_idx ON release(name)");
_session.execute("CREATE INDEX track_name_idx ON track(name)");
}
catch(std::exception& e) {
LMS_LOG(MOD_DB, SEV_ERROR) << "Cannot create tables: " << e.what();
LMS_LOG(DB, ERROR) << "Cannot create tables: " << e.what();
}
_dbBackend.executeSql("pragma journal_mode=WAL");
{
Wt::Dbo::Transaction transaction(_session);
_session.execute("PRAGMA journal_mode=WAL");
}
_users = new UserDatabase(_session);
}
@@ -133,7 +142,7 @@ Handler::getUser(const Wt::Auth::User& authUser)
{
if (!authUser.isValid()) {
LMS_LOG(MOD_DB, SEV_ERROR) << "Handler::getUser: invalid authUser";
LMS_LOG(DB, ERROR) << "Handler::getUser: invalid authUser";
return User::pointer();
}
@@ -149,5 +158,18 @@ Handler::getUser(const Wt::Auth::User& authUser)
return user;
}
Wt::Dbo::SqlConnectionPool*
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());
connection->executeSql("pragma journal_mode=WAL");
// connection->setProperty("show-queries", "true");
return new Wt::Dbo::FixedSqlConnectionPool(connection, 1);
}
} // namespace Database
+4 -4
View File
@@ -23,7 +23,8 @@
#include <boost/filesystem.hpp>
#include <Wt/Dbo/Dbo>
#include <Wt/Dbo/backend/Sqlite3>
#include <Wt/Dbo/SqlConnectionPool>
#include <Wt/Auth/Dbo/UserDatabase>
#include <Wt/Auth/Login>
#include <Wt/Auth/PasswordService>
@@ -39,7 +40,7 @@ class Handler
{
public:
Handler(boost::filesystem::path db);
Handler(Wt::Dbo::SqlConnectionPool& connectionPool);
~Handler();
Wt::Dbo::Session& getSession() { return _session; }
@@ -56,12 +57,11 @@ class Handler
static const Wt::Auth::AuthService& getAuthService();
static const Wt::Auth::PasswordService& getPasswordService();
static Wt::Dbo::SqlConnectionPool* createConnectionPool(boost::filesystem::path db);
private:
Wt::Dbo::backend::Sqlite3 _dbBackend;
Wt::Dbo::Session _session;
UserDatabase* _users;
Wt::Auth::Login _login;
+56 -1
View File
@@ -21,11 +21,40 @@
#include "Types.hpp"
static std::string pathsToString(const std::vector<boost::filesystem::path>& paths)
{
std::ostringstream oss;
bool first = true;
for (auto& path : paths)
{
if (!first)
oss << " ";
oss << path.string();
first = false;
}
return oss.str();
}
static std::vector<boost::filesystem::path> stringToPaths(const std::string value)
{
std::vector<std::string> res;
std::istringstream iss(value);
std::copy(std::istream_iterator<std::string>(iss), std::istream_iterator<std::string>(), std::back_inserter(res));
return std::vector<boost::filesystem::path>(res.begin(), res.end());
}
namespace Database {
MediaDirectorySettings::MediaDirectorySettings()
: _manualScanRequested(false),
_updatePeriod(Never)
_updatePeriod(Never),
_audioFileExtensions(".mp3 .ogg .oga .aac .m4a .flac .wav .wma .aif .aiff .ape .mpc .shn"),
_videoFileExtensions(".flv .avi .mpg .mpeg .mp4 .m4v .mkv .mov .wmv .ogv .divx .m2ts")
{
}
@@ -48,6 +77,31 @@ MediaDirectorySettings::get(Wt::Dbo::Session& session)
return res;
}
std::vector<boost::filesystem::path>
MediaDirectorySettings::getAudioFileExtensions(void) const
{
return stringToPaths(_audioFileExtensions);
}
std::vector<boost::filesystem::path>
MediaDirectorySettings::getVideoFileExtensions(void) const
{
return stringToPaths(_videoFileExtensions);
}
void
MediaDirectorySettings::setAudioFileExtensions(std::vector<boost::filesystem::path> extensions)
{
_audioFileExtensions = pathsToString(extensions);
}
void
MediaDirectorySettings::setVideoFileExtensions(std::vector<boost::filesystem::path> extensions)
{
_videoFileExtensions = pathsToString(extensions);
}
MediaDirectory::pointer
MediaDirectory::create(Wt::Dbo::Session& session, boost::filesystem::path p, Type type)
{
@@ -84,4 +138,5 @@ MediaDirectory::get(Wt::Dbo::Session& session, boost::filesystem::path p, Type t
return session.find<MediaDirectory>().where("path = ?").where("type = ?").bind( p.string()).bind(type);
}
} // namespace Database
+8
View File
@@ -55,6 +55,8 @@ class MediaDirectorySettings
void setUpdateStartTime(boost::posix_time::time_duration dur) { _updateStartTime = dur;}
void setLastUpdate(boost::posix_time::ptime time) { _lastUpdate = time; }
void setLastScan(boost::posix_time::ptime time) { _lastScan = time; }
void setAudioFileExtensions(std::vector<boost::filesystem::path> extensions);
void setVideoFileExtensions(std::vector<boost::filesystem::path> extensions);
// Read accessors
bool getManualScanRequested(void) const { return _manualScanRequested; }
@@ -62,6 +64,8 @@ class MediaDirectorySettings
boost::posix_time::time_duration getUpdateStartTime(void) const { return _updateStartTime; }
boost::posix_time::ptime getLastUpdated(void) const { return _lastUpdate; }
boost::posix_time::ptime getLastScan(void) const { return _lastScan; }
std::vector<boost::filesystem::path> getAudioFileExtensions(void) const;
std::vector<boost::filesystem::path> getVideoFileExtensions(void) const;
template<class Action>
void persist(Action& a)
@@ -69,6 +73,8 @@ class MediaDirectorySettings
Wt::Dbo::field(a, _manualScanRequested, "manual_scan_requested");
Wt::Dbo::field(a, _updatePeriod, "update_period");
Wt::Dbo::field(a, _updateStartTime, "update_start_time");
Wt::Dbo::field(a, _audioFileExtensions, "audio_file_extensions");
Wt::Dbo::field(a, _videoFileExtensions, "video_file_extensions");
Wt::Dbo::field(a, _lastUpdate, "last_update");
Wt::Dbo::field(a, _lastScan, "last_scan");
Wt::Dbo::hasMany(a, _mediaDirectories, Wt::Dbo::ManyToOne, "media_directory_settings");
@@ -79,6 +85,8 @@ class MediaDirectorySettings
bool _manualScanRequested; // Immadiate scan has been requested by user
UpdatePeriod _updatePeriod; // How long between updates
boost::posix_time::time_duration _updateStartTime; // Time of day to begin the update
std::string _audioFileExtensions; // Extension of the audio files to be scanned
std::string _videoFileExtensions; // Extension of the video files to be scanned
boost::posix_time::ptime _lastUpdate; // last time the database has changed
boost::posix_time::ptime _lastScan; // last time the database has been scanned
Wt::Dbo::collection< Wt::Dbo::ptr<MediaDirectory> > _mediaDirectories; // list of media directories
+35 -11
View File
@@ -22,6 +22,19 @@
namespace Database
{
static std::ostream& operator<<(std::ostream& ost, const std::vector< Wt::Dbo::dbo_default_traits::IdType>& ids)
{
const char *sep = "";
for (auto id : ids)
{
ost << sep << std::to_string(id);
sep = ",";
}
return ost;
}
SqlQuery generatePartialQuery(SearchFilter& filter)
{
SqlQuery sqlQuery;
@@ -33,8 +46,6 @@ SqlQuery generatePartialQuery(SearchFilter& filter)
for (auto nameLikeMatch : nameLikeMatches)
{
// Artist
switch (nameLikeMatch.first)
{
case SearchFilter::Field::Artist:
@@ -62,7 +73,8 @@ SqlQuery generatePartialQuery(SearchFilter& filter)
}
// Process id exact match parameters
// Id list may be long, we do not bind the parameters
// Safe since we already known these are just ints
for (auto idMatch : filter.idMatch)
{
WhereClause idWhereClause;
@@ -70,20 +82,32 @@ SqlQuery generatePartialQuery(SearchFilter& filter)
switch (idMatch.first)
{
case SearchFilter::Field::Artist:
for (auto id : idMatch.second)
idWhereClause.Or( WhereClause("a.id = ?") ).bind( std::to_string(id));
{
std::ostringstream oss;
oss << "a.id IN (" << idMatch.second << ")";
idWhereClause.Or(oss.str());
}
break;
case SearchFilter::Field::Release:
for (auto id : idMatch.second)
idWhereClause.Or( WhereClause("r.id = ?") ).bind( std::to_string(id));
{
std::ostringstream oss;
oss << "r.id IN (" << idMatch.second << ")";
idWhereClause.Or(oss.str());
}
break;
case SearchFilter::Field::Genre:
for (auto id : idMatch.second)
idWhereClause.Or( WhereClause("g.id = ?") ).bind( std::to_string(id));
{
std::ostringstream oss;
oss << "g.id IN (" << idMatch.second << ")";
idWhereClause.Or(oss.str());
}
break;
case SearchFilter::Field::Track:
for (auto id : idMatch.second)
idWhereClause.Or( WhereClause("t.id = ?") ).bind( std::to_string(id));
{
std::ostringstream oss;
oss << "t.id IN (" << idMatch.second << ")";
idWhereClause.Or(oss.str());
}
break;
}
+8 -1
View File
@@ -47,6 +47,7 @@ class SearchFilter
SearchFilter() {}
// Helpers
static SearchFilter IdMatch( const IdMatchType& _idMatch )
{
return SearchFilter(_idMatch);
@@ -57,12 +58,18 @@ class SearchFilter
return SearchFilter(_nameLikeMatch);
}
// Single ID match
static SearchFilter ById(Field field, Wt::Dbo::dbo_default_traits::IdType id)
{
return SearchFilter({{field, {id} }});
}
// The filter is a AND of the following conditions:
// ((Field1.name LIKE STR1-1 OR Field1.name LIKE STR1-2 ...) OR (Field2.name LIKE STR2-1 OR Field2.name LIKE STR2-2 ...) ...
NameLikeMatchType nameLikeMatch;
// ((Field1.id = ID1-1 OR Field1.id = ID1-2 ... ) AND ((Field2.id = ID2-1 OR Field2.id = ID2-2 ... ) ...
// (Field1.id IN (ID1-1,ID1-2 ... ) AND (Field2.id IN (ID2-1,ID2-2 ... ) ...
IdMatchType idMatch;
private:
+8
View File
@@ -77,6 +77,14 @@ Track::create(Wt::Dbo::Session& session, const boost::filesystem::path& p)
return session.add(new Track(p) );
}
std::vector<boost::filesystem::path>
Track::getAllPaths(Wt::Dbo::Session& session)
{
Wt::Dbo::Transaction transaction(session);
Wt::Dbo::collection<std::string> res = session.query<std::string>("SELECT file_path from track");
return std::vector<boost::filesystem::path>(res.begin(), res.end());
}
std::vector< Genre::pointer >
Track::getGenres(void) const
{
+2 -3
View File
@@ -52,8 +52,7 @@ class Genre
static pointer getByName(Wt::Dbo::Session& session, const std::string& name);
static pointer getNone(Wt::Dbo::Session& session);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
static Wt::Dbo::collection<pointer> getAll(Wt::Dbo::Session& session);
static Wt::Dbo::collection<Genre::pointer> getAll(Wt::Dbo::Session& session);
// MVC models for the user interface
// Genre ID, name, track count
typedef boost::tuple<id_type, std::string, int> UIQueryResult;
@@ -97,7 +96,6 @@ class Track
enum class CoverType
{
Embedded, // Contains embedded cover
ExternalFile, // Cover is in an external file
None, // No local cover available
};
@@ -107,6 +105,7 @@ class Track
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID);
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session);
static std::vector<boost::filesystem::path> getAllPaths(Wt::Dbo::Session& session);
// Utility fonctions
// MVC models for the user interface
+8
View File
@@ -48,4 +48,12 @@ Video::getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p)
return session.find<Video>().where("path = ?").bind(p.string());
}
std::vector<boost::filesystem::path>
Video::getAllPaths(Wt::Dbo::Session& session)
{
Wt::Dbo::Transaction transaction(session);
Wt::Dbo::collection<std::string> res = session.query<std::string>("SELECT path from video");
return std::vector<boost::filesystem::path>(res.begin(), res.end());
}
} // namespace Video
+1 -1
View File
@@ -43,7 +43,7 @@ class Video
// Find utilities
static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session);
static Wt::Dbo::collection< pointer > getByParentPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
static std::vector<boost::filesystem::path> getAllPaths(Wt::Dbo::Session& session);
// Create utility
static pointer create(Wt::Dbo::Session& session, const boost::filesystem::path& p);
+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.
*
@@ -17,35 +17,44 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EXTRACTOR_HPP
#define EXTRACTOR_HPP
#pragma once
#include <extractor.h>
#include <vector>
#include "MetaData.hpp"
#include <boost/filesystem/path.hpp>
#include <Magick++.h>
namespace MetaData
namespace Image
{
// Implements GNU libextractor library
class Extractor : public Parser
enum class Format
{
JPEG,
};
std::string format_to_mimeType(Format format);
void init(const char *path);
class Image
{
public:
Extractor();
~Extractor();
// input
bool load(const std::vector<unsigned char>& rawData);
bool load(boost::filesystem::path p);
void parse(const boost::filesystem::path& p, Items& items);
// Operations
bool scale(std::size_t size);
bool parseCover(const boost::filesystem::path& p, GenericData& data);
// output
void save(std::vector<unsigned char>& rawData, Format format) const;
private:
struct EXTRACTOR_PluginList *_plugins;
Magick::Image _image;
};
} // namespace MetaData
#endif
} // namespace Image
-111
View File
@@ -1,111 +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 "logger/Logger.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) {
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Header: bad magic ('" << std::hex << std::setfill('0') << std::setw(8) << decode32(&buffer[0]) << "' instead of '" << std::hex << std::setfill('0') << std::setw(8) << _magic << "')";
return false;
}
else
{
_dataSize = decode32(&buffer[4]);
if (_dataSize > max_data_size)
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Header: msg too big (" << _dataSize << ")!";
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,
const boost::filesystem::path& dbPath)
: _closing(false),
_socket(ioService, context),
_connectionManager(manager),
_requestHandler(dbPath)
{
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,
const boost::filesystem::path& dbPath);
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(boost::filesystem::path dbPath)
: _db( dbPath ),
_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 <boost/filesystem.hpp>
#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(boost::filesystem::path dbPath);
~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,
boost::filesystem::path dbPath)
:
_acceptor(_ioService, bindEndpoint, true /*SO_REUSEADDR*/),
_connectionManager(),
_context(boost::asio::ssl::context::tlsv1_server),
_dbPath(dbPath)
{
_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, _dbPath);
_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
-81
View File
@@ -1,81 +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 <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,
boost::filesystem::path dbPath);
// 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
boost::filesystem::path _dbPath;
};
} // namespace Server
} // namespace LmsAPI
#endif
+26 -95
View File
@@ -17,105 +17,36 @@
* 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"
Logger&
Logger::instance()
std::string getModuleName(Module mod)
{
static Logger instance;
return instance;
}
Logger::Logger()
{
// Initialiaz loggers
static const std::vector<Module> modules =
switch (mod)
{
MOD_AV,
MOD_COVER,
MOD_DB,
MOD_DBUPDATER,
MOD_MAIN,
MOD_METADATA,
MOD_REMOTE,
MOD_SERVICE,
MOD_TRANSCODE,
MOD_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"))
{
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
)
);
case Module::AV: return "AV";
case Module::COVER: return "COVER";
case Module::DB: return "DB";
case Module::DBUPDATER: return "DB UPDATER";
case Module::MAIN: return "MAIN";
case Module::METADATA: return "METADATA";
case Module::REMOTE: return "REMOTE";
case Module::SERVICE: return "SERVICE";
case Module::TRANSCODE: return "TRANSCODE";
case Module::UI: return "UI";
}
if (ConfigReader::instance().getBool("main.logger.console.enable"))
{
boost::log::add_console_log(std::cout,
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
)
);
}
boost::log::core::get()->set_filter
(
boost::log::expressions::attr<Severity>("Severity") <= ConfigReader::instance().getULong("main.logger.level")
);
return "";
}
std::string getSeverityName(Severity sev)
{
switch (sev)
{
case Severity::FATAL: return "fatal";
case Severity::ERROR: return "error";
case Severity::WARNING: return "warning";
case Severity::INFO: return "info";
case Severity::DEBUG: return "debug";
}
return "";
}
+25 -99
View File
@@ -20,113 +20,39 @@
#ifndef LOGGER_HPP__
#define LOGGER_HPP__
#include <map>
#include <Wt/WServer>
#include <Wt/WApplication>
#include <Wt/WLogger>
#include <boost/log/expressions/keyword_fwd.hpp>
#include <boost/log/expressions/keyword.hpp>
#include <string>
#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 Severity
enum class Severity
{
SEV_CRIT = 2,
SEV_ERROR = 3,
SEV_WARNING = 4,
SEV_NOTICE = 5,
SEV_INFO = 6,
SEV_DEBUG = 7,
FATAL,
ERROR,
WARNING,
INFO,
DEBUG,
};
enum Module
enum class Module
{
MOD_AV = 0,
MOD_COVER,
MOD_DB,
MOD_DBUPDATER,
MOD_MAIN,
MOD_METADATA,
MOD_REMOTE,
MOD_SERVICE,
MOD_TRANSCODE,
MOD_UI,
AV,
COVER,
DB,
DBUPDATER,
MAIN,
METADATA,
REMOTE,
SERVICE,
TRANSCODE,
UI,
};
BOOST_LOG_ATTRIBUTE_KEYWORD(module, "Module", Module)
std::string getModuleName(Module mod);
std::string getSeverityName(Severity sev);
class Logger
{
public:
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
static Logger& instance();
//[ example_tutorial_file_advanced
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__
#define LMS_LOG(module, level) Wt::log(getSeverityName(Severity::level)) << Wt::WLogger::sep << "[" << getModuleName(Module::module) << "]" << Wt::WLogger::sep
#endif
+38 -46
View File
@@ -20,22 +20,20 @@
#include <boost/filesystem.hpp>
#include "config/config.h"
#include "config/ConfigReader.hpp"
#include "transcode/AvConvTranscoder.hpp"
#include "av/Common.hpp"
#include "av/AvInfo.hpp"
#include "av/AvTranscoder.hpp"
#include "logger/Logger.hpp"
#include "cover/CoverArtGrabber.hpp"
#include "image/Image.hpp"
#include "ui/LmsApplication.hpp"
#include "service/ServiceManager.hpp"
#include "service/DatabaseUpdateService.hpp"
#include "service/UserInterfaceService.hpp"
#if defined HAVE_LMSAPI
#include "service/LmsAPIServerService.hpp"
#endif
#include <Wt/WServer>
int main(int argc, char* argv[])
{
int res = EXIT_FAILURE;
assert(argc > 0);
@@ -43,63 +41,57 @@ int main(int argc, char* argv[])
try
{
// TODO generate a nice command line help with args
// Open configuration file
boost::filesystem::path configFile("/etc/lms.conf"); // TODO
if (argc > 1)
configFile = boost::filesystem::path(argv[1]);
Wt::WServer server(argv[0]);
server.setServerConfiguration (argc, argv);
if ( !boost::filesystem::exists(configFile))
{
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();
Wt::WServer::instance()->logger().configure("*"); // log everything
Service::ServiceManager& serviceManager = Service::ServiceManager::instance();
// lib init
Image::init(argv[0]);
Av::AvInit();
Transcode::AvConvTranscoder::init();
Av::Transcoder::init();
Database::Handler::configureAuth();
LMS_LOG(MOD_MAIN, SEV_INFO) << "Starting services...";
// Initializing a connection pool to the database that will be shared along services
std::unique_ptr<Wt::Dbo::SqlConnectionPool> connectionPool( Database::Handler::createConnectionPool("/var/lms/lms.db")); // TODO use $datadir from autotools
serviceManager.startService( std::make_shared<Service::DatabaseUpdateService>() );
serviceManager.startService( std::make_shared<Service::UserInterfaceService>(boost::filesystem::path(argv[0])));
#if defined HAVE_LMSAPI
serviceManager.startService( std::make_shared<Service::LmsAPIService>( ));
#endif
serviceManager.add( std::make_shared<Service::DatabaseUpdateService>(*connectionPool));
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;
}
// 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)
{
LMS_LOG(MOD_MAIN, SEV_CRIT) << "Caught a WServer::Exception: " << e.what();
std::cerr << "Caught a WServer::Exception: " << e.what();
}
catch( std::exception& e)
{
LMS_LOG(MOD_MAIN, SEV_CRIT) << "Caught std::exception: " << e.what();
std::cerr << "Caught std::exception: " << e.what();
}
return res;
+106 -125
View File
@@ -23,7 +23,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "av/InputFormatContext.hpp"
#include "av/AvInfo.hpp"
#include "logger/Logger.hpp"
@@ -32,154 +32,135 @@
namespace MetaData
{
void
bool
AvFormat::parse(const boost::filesystem::path& p, Items& items)
{
try {
Av::MediaFile mediaFile(p);
Av::InputFormatContext input(p);
input.findStreamInfo(); // needed by input.getDurationSecs
if (!mediaFile.open())
return false;
std::map<std::string, std::string> metadata;
input.getMetadata().get(metadata);
if (!mediaFile.scan())
return false;
// HACK or OGG files
// If we did not find tags, searched metadata in streams
if (metadata.empty())
std::map<std::string, std::string> metadata = mediaFile.getMetaData();
// Stream info
{
std::vector<AudioStream> audioStreams;
std::vector<Av::Stream> streams = mediaFile.getStreams(Av::Stream::Type::Audio);
for (Av::Stream& stream : streams)
{
// Get input streams
std::vector<Av::Stream> streams = input.getStreams();
AudioStream audioStream;
audioStream.desc = stream.desc;
audioStream.bitRate = stream.bitrate;
BOOST_FOREACH(Av::Stream& stream, streams)
{
stream.getMetadata().get(metadata);
if (!metadata.empty())
break;
}
audioStreams.push_back(audioStream);
}
if (!audioStreams.empty())
items.insert( std::make_pair(MetaData::Type::AudioStreams, audioStreams));
}
// Stream info
{
std::vector<VideoStream> videoStreams;
std::vector<Av::Stream> streams = mediaFile.getStreams(Av::Stream::Type::Video);
for (Av::Stream& stream : streams)
{
std::vector<Av::Stream> avStreams = input.getStreams();
std::vector<AudioStream> audioStreams;
std::vector<VideoStream> videoStreams;
std::vector<SubtitleStream> subtitleStreams;
BOOST_FOREACH(Av::Stream& avStream, avStreams)
{
switch(avStream.getCodecContext().getType())
{
case AVMEDIA_TYPE_VIDEO:
if (!avStream.hasAttachedPic())
{
VideoStream stream;
stream.bitRate = avStream.getCodecContext().getBitRate();
videoStreams.push_back(stream);
}
break;
case AVMEDIA_TYPE_AUDIO:
{
AudioStream stream;
stream.nbChannels = avStream.getCodecContext().getNbChannels();
stream.bitRate = avStream.getCodecContext().getBitRate();
audioStreams.push_back(stream);
}
break;
case AVMEDIA_TYPE_SUBTITLE:
{
subtitleStreams.push_back( SubtitleStream() );
}
break;
default:
break;
}
}
if (!videoStreams.empty())
items.insert( std::make_pair(MetaData::Type::VideoStreams, videoStreams));
if (!audioStreams.empty())
items.insert( std::make_pair(MetaData::Type::AudioStreams, audioStreams));
if (!subtitleStreams.empty())
items.insert( std::make_pair(MetaData::Type::SubtitleStreams, subtitleStreams));
VideoStream videoStream;
videoStream.desc = stream.desc;
videoStream.bitRate = stream.bitrate;
videoStreams.push_back(videoStream);
}
// Duration
items.insert( std::make_pair(MetaData::Type::Duration, boost::posix_time::time_duration( boost::posix_time::seconds( input.getDurationSecs() )) ));
if (!videoStreams.empty())
items.insert( std::make_pair(MetaData::Type::VideoStreams, videoStreams));
}
// Cover
items.insert( std::make_pair(MetaData::Type::HasCover, input.getNbPictures() > 0));
{
std::vector<SubtitleStream> subtitleStreams;
// Embedded MetaData
// Make sure to convert strings into UTF-8
std::map<std::string, std::string>::const_iterator it;
for (it = metadata.begin(); it != metadata.end(); ++it)
std::vector<Av::Stream> streams = mediaFile.getStreams(Av::Stream::Type::Subtitle);
for (Av::Stream& stream : streams)
{
if (boost::iequals(it->first, "artist"))
items.insert( std::make_pair(MetaData::Type::Artist, string_trim( string_to_utf8(it->second)) ));
else if (boost::iequals(it->first, "album"))
items.insert( std::make_pair(MetaData::Type::Album, string_trim( string_to_utf8(it->second)) ));
else if (boost::iequals(it->first, "title"))
items.insert( std::make_pair(MetaData::Type::Title, string_trim( string_to_utf8(it->second)) ));
else if (boost::iequals(it->first, "track")) {
std::size_t number;
if (readAs<std::size_t>(it->second, number))
items.insert( std::make_pair(MetaData::Type::TrackNumber, number ));
}
else if (boost::iequals(it->first, "disc"))
{
std::size_t number;
if (readAs<std::size_t>(it->second, number))
items.insert( std::make_pair(MetaData::Type::DiscNumber, number ));
}
else if (boost::iequals(it->first, "date")
SubtitleStream subtitleStream;
subtitleStream.desc = stream.desc;
subtitleStreams.push_back(subtitleStream);
}
if (!subtitleStreams.empty())
items.insert( std::make_pair(MetaData::Type::SubtitleStreams, subtitleStreams));
}
// Duration
items.insert( std::make_pair(MetaData::Type::Duration, mediaFile.getDuration() ));
// Cover
items.insert( std::make_pair(MetaData::Type::HasCover, mediaFile.hasAttachedPictures()));
// Embedded MetaData
// Make sure to convert strings into UTF-8
std::map<std::string, std::string>::const_iterator it;
for (it = metadata.begin(); it != metadata.end(); ++it)
{
if (boost::iequals(it->first, "artist"))
items.insert( std::make_pair(MetaData::Type::Artist, string_trim( string_to_utf8(it->second)) ));
else if (boost::iequals(it->first, "album"))
items.insert( std::make_pair(MetaData::Type::Album, string_trim( string_to_utf8(it->second)) ));
else if (boost::iequals(it->first, "title"))
items.insert( std::make_pair(MetaData::Type::Title, string_trim( string_to_utf8(it->second)) ));
else if (boost::iequals(it->first, "track")) {
std::size_t number;
if (readAs<std::size_t>(it->second, number))
items.insert( std::make_pair(MetaData::Type::TrackNumber, number ));
}
else if (boost::iequals(it->first, "disc"))
{
std::size_t number;
if (readAs<std::size_t>(it->second, number))
items.insert( std::make_pair(MetaData::Type::DiscNumber, number ));
}
else if (boost::iequals(it->first, "date")
|| boost::iequals(it->first, "year")
|| boost::iequals(it->first, "WM/Year"))
{
boost::posix_time::ptime p;
if (readAsPosixTime(it->second, p))
items.insert( std::make_pair(MetaData::Type::Date, p));
}
else if (boost::iequals(it->first, "TDOR") // Original release time (ID3v2 2.4)
|| boost::iequals(it->first, "TORY")) // Original release year
{
boost::posix_time::ptime p;
if (readAsPosixTime(it->second, p))
items.insert( std::make_pair(MetaData::Type::OriginalDate, p));
}
else if (boost::iequals(it->first, "genre"))
{
std::list<std::string> genres;
if (readList(it->second, ";,", genres))
items.insert( std::make_pair(MetaData::Type::Genres, genres));
}
else if (boost::iequals(it->first, "MusicBrainz Artist Id"))
{
items.insert( std::make_pair(MetaData::Type::MusicBrainzArtistID, string_trim( string_to_utf8(it->second)) ));
}
else if (boost::iequals(it->first, "MusicBrainz Album Id"))
{
items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, string_trim( string_to_utf8(it->second)) ));
}
/* else
LMS_LOG(MOD_METADATA, SEV_DEBUG) << "key = " << it->first << ", value = " << it->second;
*/
{
boost::posix_time::ptime p;
if (readAsPosixTime(it->second, p))
items.insert( std::make_pair(MetaData::Type::Date, p));
}
else if (boost::iequals(it->first, "TDOR") // Original release time (ID3v2 2.4)
|| boost::iequals(it->first, "TORY")) // Original release year
{
boost::posix_time::ptime p;
if (readAsPosixTime(it->second, p))
items.insert( std::make_pair(MetaData::Type::OriginalDate, p));
}
else if (boost::iequals(it->first, "genre"))
{
std::list<std::string> genres;
if (readList(it->second, ";,", genres))
items.insert( std::make_pair(MetaData::Type::Genres, genres));
}
catch(std::exception &e)
{
LMS_LOG(MOD_METADATA, SEV_ERROR) << "Parsing of '" << p << "' failed!";
}
else if (boost::iequals(it->first, "MusicBrainz Artist Id"))
{
items.insert( std::make_pair(MetaData::Type::MusicBrainzArtistID, string_trim( string_to_utf8(it->second)) ));
}
else if (boost::iequals(it->first, "MusicBrainz Album Id"))
{
items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, string_trim( string_to_utf8(it->second)) ));
}
}
return true;
}
} // namespace MetaData
+1 -1
View File
@@ -30,7 +30,7 @@ class AvFormat : public Parser
{
public:
void parse(const boost::filesystem::path& p, Items& items);
bool parse(const boost::filesystem::path& p, Items& items);
private:
-226
View File
@@ -1,226 +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/date_time/gregorian/gregorian.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/foreach.hpp>
#include "Utils.hpp"
#include "Extractor.hpp"
namespace MetaData
{
extern "C" {
/**
* Type of a function that libextractor calls for each
* meta data item found.
*
* @param cls closure (user-defined)
* @param plugin_name name of the plugin that produced this value;
* special values can be used (i.e. '&lt;zlib&gt;' for zlib being
* used in the main libextractor library and yielding
* meta data).
* @param type libextractor-type describing the meta data
* @param format basic format information about data
* @param data_mime_type mime-type of data (not of the original file);
* can be NULL (if mime-type is not known)
* @param data actual meta-data found
* @param data_len number of bytes in data
* @return 0 to continue extracting, 1 to abort
*/
int processMetaData(void *cls,
const char *plugin_name,
enum EXTRACTOR_MetaType type,
enum EXTRACTOR_MetaFormat format,
const char *data_mime_type,
const char *data,
size_t data_len)
{
Items& items = *(reinterpret_cast<Items*>(cls));
switch (type) {
case EXTRACTOR_METATYPE_ARTIST:
assert( std::string(data_mime_type) == "text/plain" );
items.insert( std::make_pair(MetaData::Artist, std::string( data, data_len ? data_len - 1 : 0)) );
break;
case EXTRACTOR_METATYPE_TITLE:
assert( std::string(data_mime_type) == "text/plain" );
items.insert( std::make_pair(MetaData::Title, std::string( data, data_len ? data_len - 1 : 0)) );
break;
case EXTRACTOR_METATYPE_ALBUM:
assert( std::string(data_mime_type) == "text/plain" );
items.insert( std::make_pair(MetaData::Album, std::string( data, data_len ? data_len - 1 : 0)) );
break;
case EXTRACTOR_METATYPE_GENRE:
assert( std::string(data_mime_type) == "text/plain" );
{
std::list<std::string> genres;
if (readList(std::string( data, data_len ? data_len - 1 : 0), ";-:/,", genres))
items.insert( std::make_pair(MetaData::Genre, genres));
}
break;
/* case EXTRACTOR_METATYPE_PICTURE:
std::cout << "picture spotted" << std::endl;
break;*/
case EXTRACTOR_METATYPE_COVER_PICTURE:
{
GenericData parsedData;
const unsigned char* dataStart = reinterpret_cast<const unsigned char*>(data);
parsedData.mimeType = std::string(data_mime_type);
parsedData.data = std::vector<unsigned char>( &dataStart[0], &dataStart[data_len]);
items.insert( std::make_pair(MetaData::Cover, parsedData));
}
break;
/* case EXTRACTOR_METATYPE_EVENT_PICTURE:
std::cout << "Event picture spotted" << std::endl;
break;*/
/* case EXTRACTOR_METATYPE_CONTRIBUTOR_PICTURE:
std::cout << "Contrib picture spotted" << std::endl;
break;*/
/* case EXTRACTOR_METATYPE_SONG_COUNT:
// How many songs in the album
break;*/
/* case EXTRACTOR_METATYPE_AUDIO_CODEC:
assert( std::string(data_mime_type) == "text/plain" );
std::cout << "Aduio codec: " << std::string( data, data_len ? data_len - 1 : 0) << std::endl;
break;*/
case EXTRACTOR_METATYPE_PUBLICATION_YEAR:
assert( std::string(data_mime_type) == "text/plain" );
std::cout << "publication year = " << std::string( data, data_len ? data_len - 1 : 0) << std::endl;
break;
case EXTRACTOR_METATYPE_PUBLICATION_DATE:
assert( std::string(data_mime_type) == "text/plain" );
std::cout << "publication date = " << std::string( data, data_len ? data_len - 1 : 0) << std::endl;
break;
case EXTRACTOR_METATYPE_ORIGINAL_RELEASE_YEAR:
assert( std::string(data_mime_type) == "text/plain" );
std::cout << "original release year = " << std::string( data, data_len ? data_len - 1 : 0) << std::endl;
break;
case EXTRACTOR_METATYPE_CREATION_TIME:
assert( std::string(data_mime_type) == "text/plain" );
{
boost::posix_time::ptime p;
if (readAs<boost::posix_time::ptime>(std::string( data, data_len ? data_len - 1 : 0), p))
items.insert( std::make_pair(MetaData::CreationTime, p));
else if (readAsPosixTime(std::string( data, data_len ? data_len - 1 : 0), p))
items.insert( std::make_pair(MetaData::CreationTime, p));
}
break;
case EXTRACTOR_METATYPE_DURATION:
assert( std::string(data_mime_type) == "text/plain" );
{
boost::posix_time::time_duration duration;
if (readAs<boost::posix_time::time_duration>( std::string( data, data_len ? data_len - 1 : 0), duration))
items.insert( std::make_pair(MetaData::Duration, duration) );
}
break;
case EXTRACTOR_METATYPE_TRACK_NUMBER:
assert( std::string(data_mime_type) == "text/plain" );
{
std::size_t number(0);
if (readAs<size_t>( std::string( data, data_len ? data_len - 1 : 0), number))
items.insert( std::make_pair(MetaData::TrackNumber, number) );
}
break;
case EXTRACTOR_METATYPE_DISC_NUMBER:
assert( std::string(data_mime_type) == "text/plain" );
{
std::size_t number(0);
if (readAs<size_t>( std::string( data, data_len ? data_len - 1 : 0), number))
items.insert( std::make_pair(MetaData::DiscNumber, number) );
}
break;
default:
/* if (std::string(data_mime_type) == "text/plain")
std::cout << "TYPE = " << type << ", data = '" << std::string(data, data_len ? data_len - 1 : 0) << "'" << std::endl;
else
std::cout << "TYPE = " << type << ", data_mime_type = '" << data_mime_type << "', data len = " << data_len << std::endl;
*/
break;
}
return 0;
}
};
Extractor::Extractor()
: _plugins( nullptr )
{
// _plugins = EXTRACTOR_plugin_add_config (nullptr, "mp3:ogg:flac:wav:gstreamer", EXTRACTOR_OPTION_DEFAULT_POLICY);
}
Extractor::~Extractor()
{
}
void
Extractor::parse(const boost::filesystem::path& p, Items& items)
{
if (boost::filesystem::is_regular(p)) {
_plugins = EXTRACTOR_plugin_add_defaults (EXTRACTOR_OPTION_DEFAULT_POLICY);
if (_plugins == nullptr) {
throw std::runtime_error("EXTRACTOR_plugin_add_config failed!");
}
EXTRACTOR_extract (_plugins, p.string().c_str(), NULL, 0, &processMetaData, &items);
EXTRACTOR_plugin_remove_all (_plugins);
}
}
bool
Extractor::parseCover(const boost::filesystem::path& p, GenericData& data)
{
bool res = false;
Items items;
if (boost::filesystem::is_regular(p)) {
_plugins = EXTRACTOR_plugin_add_defaults (EXTRACTOR_OPTION_DEFAULT_POLICY);
if (_plugins == nullptr) {
throw std::runtime_error("EXTRACTOR_plugin_add_config failed!");
}
EXTRACTOR_extract (_plugins, p.string().c_str(), NULL, 0, &processMetaData, &items);
EXTRACTOR_plugin_remove_all (_plugins);
if (items.find(MetaData::Cover) != items.end()) {
data = boost::any_cast<GenericData>(items[MetaData::Cover]);
res = true;
}
}
return res;
}
} // namespace MetaData
+10 -6
View File
@@ -48,17 +48,21 @@ namespace MetaData
};
// Used by Streams
struct AudioStream {
std::size_t nbChannels;
struct AudioStream
{
std::string desc;
std::size_t bitRate;
};
struct VideoStream {
struct VideoStream
{
std::string desc;
std::size_t bitRate;
};
struct SubtitleStream {
;
struct SubtitleStream
{
std::string desc;
};
// Type and associated data
@@ -71,7 +75,7 @@ namespace MetaData
typedef std::shared_ptr<Parser> pointer;
virtual void parse(const boost::filesystem::path& p, Items& items) = 0;
virtual bool parse(const boost::filesystem::path& p, Items& items) = 0;
};
+2 -1
View File
@@ -36,7 +36,8 @@ template<typename T>
static inline bool readAs(const std::string& str, T& data)
{
std::istringstream iss ( str );
return iss >> data;
iss >> data;
return !iss.fail();
}
+2 -24
View File
@@ -19,53 +19,31 @@
#include <boost/thread.hpp>
#include "config/ConfigReader.hpp"
#include "logger/Logger.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 {
DatabaseUpdateService::DatabaseUpdateService()
DatabaseUpdateService::DatabaseUpdateService(Wt::Dbo::SqlConnectionPool &connectionPool)
: _metadataParser(),
_databaseUpdater( ConfigReader::instance().getString("main.database.path"),
_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
DatabaseUpdateService::start(void)
{
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, starting...";
_databaseUpdater.start();
}
void
DatabaseUpdateService::stop(void)
{
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, stopping...";
_databaseUpdater.stop();
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, stopped";
}
void
DatabaseUpdateService::restart(void)
{
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, restart";
stop();
start();
}
+1 -1
View File
@@ -36,7 +36,7 @@ class DatabaseUpdateService : public Service
typedef std::shared_ptr<DatabaseUpdateService> pointer;
DatabaseUpdateService();
DatabaseUpdateService(Wt::Dbo::SqlConnectionPool &connectionPool);
// Service interface
void start(void);
-64
View File
@@ -1,64 +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/asio/ip/address.hpp>
#include "config/ConfigReader.hpp"
#include "logger/Logger.hpp"
#include "LmsAPIServerService.hpp"
namespace Service {
LmsAPIService::LmsAPIService()
: _server(
boost::asio::ip::tcp::endpoint(
boost::asio::ip::address::from_string(ConfigReader::instance().getString("remote.listen-endpoint.addr")),
ConfigReader::instance().getULong("remote.listen-endpoint.port")),
ConfigReader::instance().getString("remote.ssl-crypto.cert"),
ConfigReader::instance().getString("remote.ssl-crypto.key"),
ConfigReader::instance().getString("remote.ssl-crypto.dh"),
ConfigReader::instance().getString("main.database.path"))
{
}
void
LmsAPIService::start(void)
{
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "LmsAPIService::start, starting...";
_server.start();
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "LmsAPIService::start, started!";
}
void
LmsAPIService::stop(void)
{
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "LmsAPIService::stop, stopping...";
_server.stop();
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "LmsAPIService::stop, stopped!";
}
void
LmsAPIService::restart(void)
{
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "LmsAPIService::restart, not implemented!";
}
} // 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 REMOTE_SERVER_SERVICE_HPP
#define REMOTE_SERVER_SERVICE_HPP
#include <boost/filesystem.hpp>
#include "config/config.h"
#include "Service.hpp"
#include "lms-api/server/Server.hpp"
namespace Service {
class LmsAPIService : public Service
{
public:
LmsAPIService();
void start(void);
void stop(void);
void restart(void);
private:
LmsAPI::Server::Server _server;
};
} // namespace Service
#endif
+22 -83
View File
@@ -17,11 +17,6 @@
* 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"
namespace Service {
@@ -34,112 +29,56 @@ ServiceManager::instance()
}
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()
{
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Stopping services...";
stopServices();
stop();
}
void
ServiceManager::run()
{
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)
ServiceManager::add(Service::pointer service)
{
_services.insert(service);
service->start();
}
void
ServiceManager::stopService(Service::pointer service)
ServiceManager::del(Service::pointer service)
{
_services.erase(service);
service->stop();
_services.erase(service);
}
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();
}
void
ServiceManager::restartServices(void)
ServiceManager::restart(void)
{
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Restarting services...";
BOOST_FOREACH(Service::pointer service, _services)
for (Service::pointer service : _services)
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
+11 -24
View File
@@ -20,7 +20,6 @@
#ifndef SERVICE_CONTROLER_HPP
#define SERVICE_CONTROLER_HPP
#include <boost/asio.hpp>
#include <set>
#include "Service.hpp"
@@ -35,15 +34,15 @@ class ServiceManager
static ServiceManager& instance();
~ServiceManager();
void stopService(Service::pointer service);
void startService(Service::pointer service);
void add(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
void run();
template <class T> typename T::pointer getService();
template <class T> typename T::pointer get();
boost::mutex& mutex() { return _mutex;}
@@ -53,32 +52,20 @@ class ServiceManager
ServiceManager(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::asio::io_service _ioService;
// Listen for interesting signals
boost::asio::signal_set _signalSet;
std::set<Service::pointer> _services;
};
template <class T> typename T::pointer
ServiceManager::getService()
ServiceManager::get()
{
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)) {
return std::dynamic_pointer_cast<T>(*it);
if (typeid(*(service)) == typeid(T)) {
return std::dynamic_pointer_cast<T>(service);
}
}
return std::shared_ptr<T>();
-83
View File
@@ -1,83 +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 "UserInterfaceService.hpp"
#include "ui/LmsApplication.hpp"
#include "config/ConfigReader.hpp"
namespace Service {
UserInterfaceService::UserInterfaceService( boost::filesystem::path runAppPath)
: _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, ConfigReader::instance().getString("main.database.path")));
}
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);
void start(void);
void stop(void);
void restart(void);
private:
Wt::WServer _server;
};
} //namespace Service
#endif
-243
View File
@@ -1,243 +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 <boost/iostreams/stream.hpp>
#include <boost/process.hpp>
#include <boost/foreach.hpp>
#include "logger/Logger.hpp"
#include "AvConvTranscoder.hpp"
namespace Transcode
{
// TODO, parametrize?
const std::vector<std::string> execNames =
{
"avconv",
"ffmpeg",
};
boost::mutex AvConvTranscoder::_mutex;
boost::filesystem::path AvConvTranscoder::_avConvPath = boost::filesystem::path();
void
AvConvTranscoder::init()
{
BOOST_FOREACH(std::string execName, execNames)
{
const boost::filesystem::path p = boost::process::search_path(execName);
if (!p.empty())
{
_avConvPath = p;
break;
}
}
if (!_avConvPath.empty())
LMS_LOG(MOD_TRANSCODE, SEV_INFO) << "Using transcoder " << _avConvPath;
else
throw std::runtime_error("Cannot find any transcoder binary!");
}
//boost::filesystem::path AvConvTranscoder::_avConvPath = "";
AvConvTranscoder::AvConvTranscoder(const Parameters& parameters)
: _parameters(parameters),
_outputPipe(boost::process::create_pipe()),
_source(_outputPipe.source, boost::iostreams::close_handle),
_is(_source),
_in(&_is),
_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() << "'";
// Launch a process to handle the conversion
boost::iostreams::file_descriptor_sink sink(_outputPipe.sink, boost::iostreams::close_handle);
std::ostringstream oss;
oss << _avConvPath;
// input Offset
if (_parameters.getOffset().total_seconds() > 0)
oss << " -ss " << _parameters.getOffset().total_seconds(); // to be placed before '-i' to speed up seeking
// Input file
oss << " -i \"" << _parameters.getInputMediaFile().getPath().string() << "\"";
// Output bitrates
oss << " -b:a " << _parameters.getOutputBitrate(Stream::Audio) ;
if (_parameters.getOutputFormat().getType() == Format::Video)
oss << " -b:v " << _parameters.getOutputBitrate(Stream::Video);
// Stream mapping
{
typedef std::map<Stream::Type, Stream::Id> StreamMap;
const StreamMap& streamMap = _parameters.getInputStreams();
BOOST_FOREACH(const StreamMap::value_type& inputStream, streamMap)
{
// HACK (no subtitle support yet)
if (inputStream.first != Stream::Subtitle)
oss << " -map 0:" << inputStream.second; // 0 means input file index
}
}
// Codecs and formats
switch( _parameters.getOutputFormat().getEncoding())
{
case Format::MP3:
oss << " -f mp3";
break;
case Format::OGA:
oss << " -acodec libvorbis -f ogg";
break;
case Format::OGV:
oss << " -acodec libvorbis -ac 2 -ar 44100 -vcodec libtheora -threads 4 -f ogg";
break;
case Format::WEBMA:
oss << " -codec:a libvorbis -f webm";
break;
case Format::WEBMV:
oss << " -acodec libvorbis -ac 2 -ar 44100 -vcodec libvpx -threads 4 -f webm";
break;
case Format::M4A:
oss << " -acodec aac -f mp4 -strict experimental";
break;
case Format::M4V:
oss << " -acodec aac -strict experimental -ac 2 -ar 44100 -vcodec libx264 -f m4v";
break;
case Format::FLV:
oss << " -acodec libmp3lame -ac 2 -ar 44100 -vcodec libx264 -f flv";
break;
case Format::FLA:
oss << " -acodec libmp3lame -f flv";
break;
default:
assert(0);
}
oss << " -"; // output to stdout
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Executing '" << oss.str() << "'";
// make sure only one thread is executing this part of code
// See boost process FAQ
{
boost::lock_guard<boost::mutex> lock(_mutex);
_child = std::make_shared<boost::process::child>( boost::process::execute(
boost::process::initializers::run_exe(_avConvPath),
boost::process::initializers::set_cmd_line(oss.str()),
boost::process::initializers::bind_stdout(sink),
boost::process::initializers::close_fds_if([](int fd) { return fd != STDOUT_FILENO;})
)
);
}
}
void
AvConvTranscoder::process(std::vector<unsigned char>& output, std::size_t maxSize)
{
std::size_t readDataSize = 0;
if (_isComplete)
return;
char ch;
while(readDataSize < maxSize && _in && _in.get(ch)) {
output.push_back(ch);
readDataSize++;
_outputBytes++; // stats
}
if (!_in || _in.fail() || _in.eof()) {
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Transcode complete!";
waitChild();
_isComplete = true;
}
}
AvConvTranscoder::~AvConvTranscoder()
{
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "~AvConvTranscoder called!";
if (_in.eof())
waitChild();
else
killChild();
}
void
AvConvTranscoder::waitChild()
{
if (_child)
{
boost::system::error_code ec;
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Waiting for child...";
boost::process::wait_for_exit(*_child, ec);
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Waiting for child: OK";
if (ec)
LMS_LOG(MOD_TRANSCODE, SEV_ERROR) << "AvConvTranscoder::waitChild: error: " << ec.message();
_child.reset();
}
}
void
AvConvTranscoder::killChild()
{
if (_child)
{
boost::system::error_code ec;
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Killing child! pid = " << _child->pid;
boost::process::terminate(*_child, ec);
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Killing child DONE";
// If an error occured, force kill the child
if (ec)
LMS_LOG(MOD_TRANSCODE, SEV_ERROR) << "AvConvTranscoder::killChild: error: " << ec.message();
_child.reset();
}
}
} // namespace Transcode
-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
-127
View File
@@ -1,127 +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 <boost/foreach.hpp>
#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);
input.findStreamInfo();
// 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
BOOST_FOREACH(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;
BOOST_FOREACH(const Stream& stream, _streams)
{
if (stream.getType() == type)
res.push_back(stream);
}
return res;
}
const Stream&
InputMediaFile::getStream(Stream::Id index) const
{
BOOST_FOREACH(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
+18 -10
View File
@@ -28,6 +28,8 @@
#include <Wt/WVBoxLayout>
#include <Wt/Auth/Identity>
#include "config/config.h"
#include "logger/Logger.hpp"
#include "settings/Settings.hpp"
@@ -36,7 +38,9 @@
#include "auth/LmsAuth.hpp"
#include "audio/desktop/DesktopAudio.hpp"
#include "audio/mobile/MobileAudio.hpp"
#if HAVE_VIDEO
#include "video/VideoWidget.hpp"
#endif
#include "common/LineEdit.hpp"
#include "LmsApplication.hpp"
@@ -63,13 +67,13 @@ bool agentIsMobile()
namespace UserInterface {
Wt::WApplication*
LmsApplication::create(const Wt::WEnvironment& env, boost::filesystem::path dbPath)
LmsApplication::create(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool)
{
/*
* You could read information from the environment to decide whether
* the user has permission to start a new application
*/
return new LmsApplication(env, dbPath);
return new LmsApplication(env, connectionPool);
}
LmsApplication*
@@ -84,9 +88,9 @@ LmsApplication::instance()
* constructor so it is typically also an argument for your custom
* application constructor.
*/
LmsApplication::LmsApplication(const Wt::WEnvironment& env, boost::filesystem::path dbPath)
LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool)
: Wt::WApplication(env),
_db(dbPath),
_db(connectionPool),
_coverResource(nullptr)
{
@@ -109,6 +113,8 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, boost::filesystem::p
firstConnection = (Database::User::getAll(DboSession()).size() == 0);
}
LMS_LOG(UI, DEBUG) << "Creating root widget. First connection = " << std::boolalpha << firstConnection;
// If here is no account in the database, launch the first connection wizard
if (firstConnection)
createFirstConnectionUI();
@@ -167,21 +173,23 @@ LmsApplication::handleAuthEvent(void)
{
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);
setConfirmCloseMessage("Closing LMS. Are you sure?");
// Create a Vertical layout: top is the nav bar, bottom is the contents
Wt::WVBoxLayout *layout = new Wt::WVBoxLayout(this->root());
// Create a navigation bar with a link to a web page.
Wt::WNavigationBar *navigation = new Wt::WNavigationBar();
navigation->setTitle("LMS");
navigation->setTitle("LMS", "https://github.com/epoupon/lms");
navigation->setResponsive(true);
navigation->addStyleClass("main-nav");
Wt::WStackedWidget *contentsStack = new Wt::WStackedWidget();
contentsStack->setOverflow(Wt::WContainerWidget::OverflowAuto);
contentsStack->addStyleClass("contents");
// Setup a Left-aligned menu.
Wt::WMenu *leftMenu = new Wt::WMenu(contentsStack);
@@ -194,10 +202,10 @@ LmsApplication::handleAuthEvent(void)
else
audio = new Desktop::Audio();
VideoWidget *videoWidget = new VideoWidget();
leftMenu->addItem("Audio", audio);
leftMenu->addItem("Video", videoWidget);
#if defined HAVE_VIDEO
leftMenu->addItem("Video", new VideoWidget());
#endif
leftMenu->addItem("Settings", new Settings::Settings());
// Setup a Right-aligned menu.
@@ -236,7 +244,7 @@ LmsApplication::handleAuthEvent(void)
}
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("");
redirect("/");
+4 -2
View File
@@ -23,6 +23,8 @@
#include <boost/filesystem.hpp>
#include <Wt/WApplication>
#include <Wt/Dbo/SqlConnectionPool>
#include "database/DatabaseHandler.hpp"
#include "resource/CoverResource.hpp"
@@ -31,10 +33,10 @@ namespace UserInterface {
class LmsApplication : public Wt::WApplication
{
public:
static Wt::WApplication *create(const Wt::WEnvironment& env, boost::filesystem::path dbPath);
static Wt::WApplication *create(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool);
static LmsApplication* instance();
LmsApplication(const Wt::WEnvironment& env, boost::filesystem::path dbPath);
LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool);
// Session application data
CoverResource* getCoverResource() { return _coverResource; }
+77 -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
Wt::WHBoxLayout *currentMediaLayout = new Wt::WHBoxLayout();
@@ -172,100 +172,111 @@ AudioMediaPlayer::AudioMediaPlayer(Wt::WContainerWidget *parent)
}
void
AudioMediaPlayer::loadPlayer(void)
AudioMediaPlayer::loadPlayer(boost::filesystem::path filePath, Av::TranscodeParameters& parameters)
{
_currentFile = filePath;
_currentParameters = parameters;
_mediaPlayer->clearSources();
if (_mediaResource)
delete _mediaResource;
assert( _currentParameters );
_mediaResource = new AvConvTranscodeStreamResource( *_currentParameters, this );
_mediaResource = new AvConvTranscodeStreamResource( filePath, parameters, this );
_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
_mediaPlayer->play();
}
bool
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);
if (!track)
{
LMS_LOG(UI, INFO) << "Cannot find track id " << trackId;
return false;
}
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() || !mediaFile.scan())
{
LMS_LOG(UI, INFO) << "Cannot open file '" << path << "'";
return false;
}
// 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);
return true;
}
void
AudioMediaPlayer::handlePlayOffset(int offsetSecs)
{
if (!_currentParameters)
return;
Av::TranscodeParameters parameters = _currentParameters;
_currentParameters->setOffset( boost::posix_time::seconds(offsetSecs) );
parameters.setOffset( boost::posix_time::seconds(offsetSecs) );
loadPlayer();
_mediaPlayer->play();
loadPlayer(_currentFile, parameters);
}
void
AudioMediaPlayer::handleTrackEnded(void)
{
LMS_LOG(UI, INFO) << "Track ended!";
_playbackEnded.emit();
}
void
AudioMediaPlayer::handleTimeUpdated(void)
{
if (!_currentParameters)
return;
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() );
_curTime->setText( boost::posix_time::to_simple_string( currentTime) );
+7 -4
View File
@@ -31,7 +31,7 @@
#include "database/Types.hpp"
#include "transcode/Parameters.hpp"
#include "av/AvTranscoder.hpp"
#include "resource/AvConvTranscodeStreamResource.hpp"
#include "resource/CoverResource.hpp"
@@ -46,7 +46,7 @@ class AudioMediaPlayer : public Wt::WContainerWidget
AudioMediaPlayer(Wt::WContainerWidget *parent = 0);
// Load Media to be played
void load(Database::Track::id_type trackId);
bool load(Database::Track::id_type trackId);
// Accessors
Wt::WMediaPlayer::Encoding getEncoding() const { return _encoding; }
@@ -70,7 +70,7 @@ class AudioMediaPlayer : public Wt::WContainerWidget
void handleVolumeSliderMoved(int value);
void loadPlayer(void);
void loadPlayer(boost::filesystem::path filePath, Av::TranscodeParameters& parameters);
// Signals
Wt::Signal<void> _playbackEnded;
@@ -90,7 +90,6 @@ class AudioMediaPlayer : public Wt::WContainerWidget
Wt::WText* _mediaArtistRelease;
// Controls
std::shared_ptr<Transcode::Parameters> _currentParameters;
Wt::WPushButton* _playBtn;
Wt::WPushButton* _pauseBtn;
Wt::WSlider* _timeSlider;
@@ -98,6 +97,10 @@ class AudioMediaPlayer : public Wt::WContainerWidget
Wt::WText* _curTime;
Wt::WText* _duration;
// Transcode
boost::filesystem::path _currentFile;
Av::TranscodeParameters _currentParameters;
};
} // namespace Desktop
+11 -10
View File
@@ -215,7 +215,7 @@ _playQueue(nullptr)
_mediaPlayer->loop().connect(boost::bind(&PlayQueue::setLoop,_playQueue, _1));
_playQueue->tracksUpdated().connect(std::bind([=] () {
LMS_LOG(MOD_UI, SEV_INFO) << "Playqueue updated!";
LMS_LOG(UI, INFO) << "Playqueue updated!";
playlistSaveFromPlayqueue(CurrentQueuePlaylistName);
}));
@@ -308,14 +308,14 @@ Audio::playlistShowSaveDialog(std::string playlistName)
void
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());
Playlist::pointer playlist = Playlist::get(DboSession(), playlistName, CurrentUser());
if (playlist)
{
LMS_LOG(MOD_UI, SEV_INFO) << "Erasing playlist '" << playlistName << "'";
LMS_LOG(UI, INFO) << "Erasing playlist '" << playlistName << "'";
playlist.remove();
}
@@ -333,13 +333,13 @@ Audio::playlistSaveFromPlayqueue(std::string playlistName)
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
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;
@@ -356,7 +356,7 @@ Audio::playlistLoadToPlayqueue(std::string playlistName)
_playQueue->clear();
_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());
// 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(_popupMenuLoad);
@@ -457,7 +457,7 @@ Audio::playSelectedTracks(PlayQueueAddType addType)
{
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();
@@ -472,7 +472,7 @@ Audio::playSelectedTracks(PlayQueueAddType addType)
break;
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 (_trackView->getNbSelectedTracks() == 0)
@@ -510,7 +510,8 @@ Audio::playTrack(Track::id_type trackId, int pos)
CurrentUser().modify()->setCurPlayingTrackPos(pos);
}
_mediaPlayer->load(trackId);
if (!_mediaPlayer->load(trackId))
_playQueue->playNext();
}
} // namespace Desktop
+5 -7
View File
@@ -291,9 +291,10 @@ _trackSelector(new TrackSelector())
this->setColumnWidth(COLUMN_ID_NAME, 240);
this->setLayoutSizeAware(true);
#if WT_VERSION >= 0X03030400
this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal);
this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical);
#endif
this->setColumnHidden(COLUMN_ID_TRACK_ID, true);
_itemDelegate = new PlayQueueItemDelegate();
@@ -370,7 +371,7 @@ PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds)
{
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
for (Track::id_type trackId : trackIds)
@@ -388,10 +389,7 @@ PlayQueue::addTracks(const std::vector<Database::Track::id_type>& trackIds)
_model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole);
std::string coverUrl;
if (track->getCoverType() != Track::CoverType::None)
coverUrl = LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 64);
else
coverUrl = LmsApplication::instance()->getCoverResource()->getUnknownTrackUrl(64);
coverUrl = LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 64);
_model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole);
_model->setData(dataRow, COLUMN_ID_COVER, std::string("playqueue-cover"), Wt::StyleClassRole);
@@ -464,7 +462,7 @@ PlayQueue::playPrevious(void)
void
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())
{

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