hathor-cpp-scanner/scanner.cc

223 lines
9.2 KiB
C++

// default c++ headers
#include <cstdio>
#include <cstring>
#include <iostream>
#include <fstream>
#include <array>
#include "scanner.hh"
#include "config.hh"
#include "common.hh"
#include "hathor_mysql.hh"
#include "3rdparty/crypt_blowfish.h"
namespace fs = boost::filesystem;
using namespace std;
static FileType fileType( std::string extension ) {
for( unsigned int i = 1; i < s_fileTypeStrings.size(); i++ ) {
if( extension.compare(s_fileTypeStrings.at( i )) == 0 )
return FileType( i );
}
return FileType( Unknown );
}
/*
* hathor_handle_file( std::string file )
* Process file information and send it to MySQL
*/
static s_fileFields handle_file( std::string file ) {
s_fileFields filefields; // Structure to store file information for MySQL
fs::path current = fs::system_complete(file); // Get current file object
std::string current_path = current.parent_path().native(); // Get current path minus the file
TagLib::FileRef f( file.c_str() ); // Create TagLib object
if(!f.isNull() && f.tag()) {
filefields.artist = ( f.tag()->artist().isEmpty() ? "Unknown" : f.tag()->artist().toCString(true));
filefields.album = ( f.tag()->album().isEmpty() ? "Unknown" : f.tag()->album().toCString(true));
filefields.title = ( f.tag()->title().isEmpty() ? file.c_str() : f.tag()->title().toCString(true) );
filefields.genre = ( f.tag()->genre().isEmpty() ? "null" : f.tag()->genre().toCString(true));
filefields.comment = ( f.tag()->comment().isEmpty() ? "null" : f.tag()->comment().toCString(true));
filefields.tracknum = ( f.tag()->track() ? f.tag()->track() : 0 );
filefields.year = ( f.tag()->year() ? f.tag()->year() : 0 );
TagLib::AudioProperties *p = f.audioProperties();
filefields.bitrate = ( p->bitrate() ? p->bitrate() : 0 );
filefields.sample = ( p->sampleRate() ? p->sampleRate() : 0 );
filefields.channels = ( p->channels() ? p->channels() : 0 );
if( p->length() ) {
filefields.length = p->length();
filefields.seconds = p->length() % 60;
filefields.minutes = ( p->length() - filefields.seconds ) / 60;
} else {
filefields.length = 0;
filefields.seconds = 0;
filefields.minutes = 0;
}
TagLib::PropertyMap tags = f.file()->properties();
for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) {
for(TagLib::StringList::ConstIterator j = i->second.begin(); j != i->second.end(); ++j) {
if(i->first == "ALBUMARTIST") { filefields.albumartist = (j->isEmpty() ? "null" : j->toCString(true)); }
if(i->first == "LYRICS") { filefields.lyrics = (j->isEmpty() ? "null" : j->toCString(true)); }
if(i->first == "LABEL") { filefields.label = (j->isEmpty() ? "null" : j->toCString(true));}
}
}
++file_count;
}
else {
FILE * notagslog;
notagslog = fopen("no_media_info.log", "a");
fprintf (notagslog, "Skipping %s, no media information found ...\n",file.c_str());
printf("Skipping %s, no media information found ...\n", file.c_str());
//notagslog << file << endl;
++no_info_count;
fclose(notagslog);
}
return filefields;
}
json j = parse_file("config.json");
scanner_config s_opts = hathor_config(j);
static int handle_directory( std::string path ) {
sql::Connection *conn = get_connection(
s_opts.mysql.user.c_str(),
s_opts.mysql.pass.c_str(),
s_opts.mysql.host.c_str(),
s_opts.mysql.port
);
conn->setAutoCommit(0); // Turn autocommit off for transactions.
conn->setSchema(s_opts.mysql.db);
unsigned int maxPstmt = get_max_statements(conn);
sql::PreparedStatement *artist = conn->prepareStatement("INSERT IGNORE INTO artists (arid,name) VALUES(MD5(?),?)");
sql::PreparedStatement *album = conn->prepareStatement("INSERT IGNORE INTO albums (aid,name,arid,albumartist,label,released) VALUES(MD5(?),?,MD5(?),?,?,?)");
sql::PreparedStatement *track = conn->prepareStatement("INSERT IGNORE INTO tracks (tid,uri,arid,aid,title,track,genre,lyrics,comment,bitrate,sample,channels,length,minutes,seconds) VALUES(MD5(?),?,MD5(?),MD5(?),?,?,?,?,?,?,?,?,?,?,?)");
ofstream skipped; // Open file for adding skipped files
skipped.open("skipped_files.log", ios::out | ios::app); // open log
ofstream error_log;
error_log.open("errors.log",ios::out|ios::app);
fs::path p( fs::system_complete( path.c_str() ) ); // Get the current path we are working on
cout << p.native() << endl;
if ( !fs::exists(p) ) { // exit if path was not found
std::cout << "\nPath Not found: " << p << std::endl;
return 1;
}
fs::recursive_directory_iterator end_iter;
for (fs::recursive_directory_iterator dir_itr(p); dir_itr != end_iter; ++dir_itr) {
try {
if ( fs::is_directory( dir_itr->status() ) && !is_hidden( dir_itr->path() ) ) {
++dir_count;
printf(" * Scanning directory %s ...\n", dir_itr->path().native().c_str());
continue;
}
else if ( fs::is_regular_file( dir_itr->status() ) && !is_hidden( dir_itr->path() ) ) {
std::string extension = dir_itr->path().extension().native();
if( !extension.empty() ) {
extension.erase(extension.find('.'), extension.find('.')+1);
toLower(extension);
if( fileType(extension) == FileType( Unknown ) ) {
std::cout << " * Skipping file " << dir_itr->path().native() << " ... \n";
skipped << "[" << extension << "] " << dir_itr->path().native() << "\n";
++skip_count;
} else {
s_fileFields filefields = handle_file( dir_itr->path().native() );
char aid[1000];
std::string ayear = boost::lexical_cast<std::string>(filefields.year);
strcpy(aid,filefields.album.c_str());
strcat(aid,filefields.artist.c_str());
strcat(aid, ayear.c_str());
// Artist
artist->setString(1, filefields.artist ); // artists.arid
artist->setString(2, filefields.artist ); // artists.name
artist->executeUpdate();
// Album
album->setString(1, aid ); // albums.aid
album->setString(2, filefields.album ); // albums.name
album->setString(3, filefields.artist ); // albums.arid
album->setString(4, filefields.albumartist ); // albums.albumartist
album->setString(5, filefields.label ); // albums.label
album->setInt (6, filefields.year ); // albums.released
album->executeUpdate();
// Tracks
track->setString(1, dir_itr->path().native() ); // tracks.tid
track->setString(2, dir_itr->path().native() ); // tracks.uri
track->setString(3, filefields.artist ); // tracks.arid
track->setString(4, aid ); // tracks.aid
track->setString(5, filefields.title ); // tracks.title
track->setInt (6, filefields.tracknum ); // tracks.track
track->setString(7, filefields.genre ); // tracks.genre
track->setString(8, filefields.lyrics ); // tracks.lyrics
track->setString(9, filefields.comment ); // tracks.comment
track->setInt (10, filefields.bitrate ); // tracks.bitrate
track->setInt (11, filefields.sample ); // tracks.sample
track->setInt (12, filefields.channels ); // tracks.channels
track->setInt (13, filefields.length ); // tracks.length
track->setInt (14, filefields.minutes ); // tracks.minutes
track->setInt (15, filefields.seconds ); // tracks.seconds
track->executeUpdate();
}
} else {
//std::cout << " * Skipping file " << dir_itr->path().native() << " ... \n";
skipped << "[no extension] " << dir_itr->path().native() << "\n";
++skip_count;
}
}
else {
skipped << "[other] " << dir_itr->path().native() << "\n";
++other_count;
}
} catch (const sql::SQLException & sqlError ) {
error_log << "[SQLError] : " << dir_itr->path().filename() << " " << sqlError.what() << std::endl;
continue;
}
catch (const std::exception & ex) {
++err_count;
std::cout << dir_itr->path().filename() << " " << ex.what() << std::endl;
error_log << dir_itr->path().filename() << " " << ex.what() << std::endl;
}
}
skipped.close();
error_log.close();
conn->commit();
delete artist;
delete album;
delete track;
delete conn;
return 0;
}
int main( int argc, char *argv[] ) {
if(argc > 1) s_opts.scanner.path = fs::current_path().native();
handle_directory(s_opts.scanner.path);
std::cout << "Directorys : " << dir_count << endl;
std::cout << "Media : " << file_count << endl;
std::cout << "No tags : " << no_info_count << endl;
std::cout << "Skipped : " << skip_count << endl;
std::cout << "Other Files : " << other_count << endl;
std::cout << "Errors : " << err_count << endl;
return 0;
}