~ci-train-bot/mediascanner2/mediascanner2-ubuntu-yakkety-landing-063

« back to all changes in this revision

Viewing changes to src/extractor/MetadataExtractor.cc

  • Committer: CI Train Bot
  • Author(s): James Henstridge
  • Date: 2015-11-09 01:56:37 UTC
  • mfrom: (288.1.28 external-metadata)
  • Revision ID: ci-train-bot@canonical.com-20151109015637-q4ixft4f3l2t1r3m
Move the metadata extractor to a separate process to isolate bugs in media codecs. Fixes: #1508142
Approved by: PS Jenkins bot

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2013-2014 Canonical, Ltd.
 
3
 *
 
4
 * Authors:
 
5
 *    Jussi Pakkanen <jussi.pakkanen@canonical.com>
 
6
 *    James Henstridge <james.henstridge@canonical.com>
 
7
 *
 
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.
 
11
 *
 
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
 
15
 * details.
 
16
 *
 
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/>.
 
19
 */
 
20
 
 
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"
 
28
 
 
29
#include <glib-object.h>
 
30
#include <gio/gio.h>
 
31
 
 
32
#include <algorithm>
 
33
#include <array>
 
34
#include <cstdio>
 
35
#include <memory>
 
36
#include <string>
 
37
#include <stdexcept>
 
38
 
 
39
using namespace std;
 
40
 
 
41
namespace {
 
42
const char BUS_NAME[] = "com.canonical.MediaScanner2.Extractor";
 
43
const char BUS_PATH[] = "/com/canonical/MediaScanner2/Extractor";
 
44
 
 
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"}};
 
47
 
 
48
void validate_against_blacklist(const std::string &filename, const std::string &content_type) {
 
49
 
 
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 + ".");
 
53
    }
 
54
}
 
55
 
 
56
}
 
57
 
 
58
namespace mediascanner {
 
59
 
 
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};
 
63
 
 
64
    MetadataExtractorPrivate(GDBusConnection *bus);
 
65
    void create_proxy();
 
66
};
 
67
 
 
68
MetadataExtractorPrivate::MetadataExtractorPrivate(GDBusConnection *bus)
 
69
    : bus(reinterpret_cast<GDBusConnection*>(g_object_ref(bus)),
 
70
          g_object_unref) {
 
71
    create_proxy();
 
72
}
 
73
 
 
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));
 
82
    if (not proxy) {
 
83
        string errortxt(error->message);
 
84
        g_error_free(error);
 
85
 
 
86
        string msg = "Failed to create D-Bus proxy: ";
 
87
        msg += errortxt;
 
88
        throw runtime_error(msg);
 
89
    }
 
90
}
 
91
 
 
92
MetadataExtractor::MetadataExtractor(GDBusConnection *bus) {
 
93
    p.reset(new MetadataExtractorPrivate(bus));
 
94
}
 
95
 
 
96
MetadataExtractor::~MetadataExtractor() = default;
 
97
 
 
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);
 
101
    if (!file) {
 
102
        throw runtime_error("Could not create file object");
 
103
    }
 
104
 
 
105
    GError *error = nullptr;
 
106
    std::unique_ptr<GFileInfo, void(*)(void *)> info(
 
107
        g_file_query_info(
 
108
            file.get(),
 
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),
 
113
        g_object_unref);
 
114
    if (!info) {
 
115
        string errortxt(error->message);
 
116
        g_error_free(error);
 
117
 
 
118
        string msg("Query of file info for ");
 
119
        msg += filename;
 
120
        msg += " failed: ";
 
121
        msg += errortxt;
 
122
        throw runtime_error(msg);
 
123
    }
 
124
 
 
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.");
 
132
    }
 
133
 
 
134
    validate_against_blacklist(filename, content_type);
 
135
    MediaType type;
 
136
    if (content_type.find("audio/") == 0) {
 
137
        type = AudioMedia;
 
138
    } else if (content_type.find("video/") == 0) {
 
139
        type = VideoMedia;
 
140
    } else if (content_type.find("image/") == 0) {
 
141
        type = ImageMedia;
 
142
    } else {
 
143
        throw runtime_error(string("File ") + filename + " is not audio or video");
 
144
    }
 
145
    return DetectedFile(filename, etag, content_type, mtime, type);
 
146
}
 
147
 
 
148
MediaFile MetadataExtractor::extract(const DetectedFile &d) {
 
149
    fprintf(stderr, "Extracting metadata from %s.\n", d.filename.c_str());
 
150
 
 
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) {
 
161
        g_error_free(error);
 
162
        error = nullptr;
 
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.
 
166
        p->create_proxy();
 
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);
 
170
    }
 
171
    if (!success) {
 
172
        string errortxt(error->message);
 
173
        g_error_free(error);
 
174
 
 
175
        string msg = "ExtractMetadata D-Bus call failed: ";
 
176
        msg += errortxt;
 
177
        throw runtime_error(msg);
 
178
    }
 
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());
 
183
}
 
184
 
 
185
MediaFile MetadataExtractor::fallback_extract(const DetectedFile &d) {
 
186
    return MediaFileBuilder(d.filename).setType(d.type);
 
187
}
 
188
 
 
189
}