2
* Copyright (C) 2013-2014 Canonical, Ltd.
5
* Jussi Pakkanen <jussi.pakkanen@canonical.com>
6
* James Henstridge <james.henstridge@canonical.com>
8
* This library is free software; you can redistribute it and/or modify it under
9
* the terms of version 3 of the GNU General Public License as published
10
* by the Free Software Foundation.
12
* This library is distributed in the hope that it will be useful, but WITHOUT
13
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17
* You should have received a copy of the GNU General Public License
18
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21
#include "MetadataExtractor.hh"
22
#include "DetectedFile.hh"
23
#include "dbus-generated.h"
24
#include "dbus-marshal.hh"
25
#include "../mediascanner/MediaFile.hh"
26
#include "../mediascanner/MediaFileBuilder.hh"
27
#include "../mediascanner/internal/utils.hh"
29
#include <glib-object.h>
42
const char BUS_NAME[] = "com.canonical.MediaScanner2.Extractor";
43
const char BUS_PATH[] = "/com/canonical/MediaScanner2/Extractor";
45
// This list was obtained by grepping /usr/share/mime/audio/.
46
std::array<const char*, 4> blacklist{{"audio/x-iriver-pla", "audio/x-mpegurl", "audio/x-ms-asx", "audio/x-scpls"}};
48
void validate_against_blacklist(const std::string &filename, const std::string &content_type) {
50
auto result = std::find(blacklist.begin(), blacklist.end(), content_type);
51
if(result != blacklist.end()) {
52
throw runtime_error("File " + filename + " is of blacklisted type " + content_type + ".");
58
namespace mediascanner {
60
struct MetadataExtractorPrivate {
61
std::unique_ptr<GDBusConnection, decltype(&g_object_unref)> bus;
62
std::unique_ptr<MSExtractor, decltype(&g_object_unref)> proxy {nullptr, g_object_unref};
64
MetadataExtractorPrivate(GDBusConnection *bus);
68
MetadataExtractorPrivate::MetadataExtractorPrivate(GDBusConnection *bus)
69
: bus(reinterpret_cast<GDBusConnection*>(g_object_ref(bus)),
74
void MetadataExtractorPrivate::create_proxy() {
75
GError *error = nullptr;
76
proxy.reset(ms_extractor_proxy_new_sync(
77
bus.get(), static_cast<GDBusProxyFlags>(
78
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
79
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
80
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION),
81
BUS_NAME, BUS_PATH, nullptr, &error));
83
string errortxt(error->message);
86
string msg = "Failed to create D-Bus proxy: ";
88
throw runtime_error(msg);
92
MetadataExtractor::MetadataExtractor(GDBusConnection *bus) {
93
p.reset(new MetadataExtractorPrivate(bus));
96
MetadataExtractor::~MetadataExtractor() = default;
98
DetectedFile MetadataExtractor::detect(const std::string &filename) {
99
std::unique_ptr<GFile, void(*)(void *)> file(
100
g_file_new_for_path(filename.c_str()), g_object_unref);
102
throw runtime_error("Could not create file object");
105
GError *error = nullptr;
106
std::unique_ptr<GFileInfo, void(*)(void *)> info(
109
G_FILE_ATTRIBUTE_TIME_MODIFIED ","
110
G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE ","
111
G_FILE_ATTRIBUTE_ETAG_VALUE,
112
G_FILE_QUERY_INFO_NONE, /* cancellable */ nullptr, &error),
115
string errortxt(error->message);
118
string msg("Query of file info for ");
122
throw runtime_error(msg);
125
uint64_t mtime = g_file_info_get_attribute_uint64(
126
info.get(), G_FILE_ATTRIBUTE_TIME_MODIFIED);
127
string etag(g_file_info_get_etag(info.get()));
128
string content_type(g_file_info_get_attribute_string(
129
info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE));
130
if (content_type.empty()) {
131
throw runtime_error("Could not determine content type.");
134
validate_against_blacklist(filename, content_type);
136
if (content_type.find("audio/") == 0) {
138
} else if (content_type.find("video/") == 0) {
140
} else if (content_type.find("image/") == 0) {
143
throw runtime_error(string("File ") + filename + " is not audio or video");
145
return DetectedFile(filename, etag, content_type, mtime, type);
148
MediaFile MetadataExtractor::extract(const DetectedFile &d) {
149
fprintf(stderr, "Extracting metadata from %s.\n", d.filename.c_str());
151
GError *error = nullptr;
152
GVariant *res = nullptr;
153
gboolean success = ms_extractor_call_extract_metadata_sync(
154
p->proxy.get(), d.filename.c_str(), d.etag.c_str(),
155
d.content_type.c_str(), d.mtime, d.type, &res, nullptr, &error);
156
// If we get a synthesised "no reply" error, the server probably
157
// crashed due to a codec bug. We retry the extraction once more
158
// in case the crash was due to bad cleanup from a previous job.
159
if (!success && error->domain == G_DBUS_ERROR &&
160
error->code == G_DBUS_ERROR_NO_REPLY) {
163
fprintf(stderr, "No reply from extractor daemon, retrying once.\n");
164
// Recreate the proxy, since the old one will have bound to
165
// the old instance's unique name.
167
success = ms_extractor_call_extract_metadata_sync(
168
p->proxy.get(), d.filename.c_str(), d.etag.c_str(),
169
d.content_type.c_str(), d.mtime, d.type, &res, nullptr, &error);
172
string errortxt(error->message);
175
string msg = "ExtractMetadata D-Bus call failed: ";
177
throw runtime_error(msg);
179
// Place variant in a unique_ptr so it is guaranteed to be unrefed.
180
std::unique_ptr<GVariant,decltype(&g_variant_unref)> result(
181
res, g_variant_unref);
182
return media_from_variant(result.get());
185
MediaFile MetadataExtractor::fallback_extract(const DetectedFile &d) {
186
return MediaFileBuilder(d.filename).setType(d.type);