run_decoder.cxx 5.38 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13
 * http://www.musicpd.org
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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.
14 15 16 17
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 19
 */

20
#include "ConfigGlue.hxx"
21
#include "event/Thread.hxx"
22
#include "decoder/DecoderList.hxx"
23
#include "decoder/DecoderPlugin.hxx"
24
#include "decoder/DecoderAPI.hxx" /* for class StopDecoder */
25
#include "DumpDecoderClient.hxx"
Max Kellermann's avatar
Max Kellermann committed
26 27
#include "input/Init.hxx"
#include "input/InputStream.hxx"
28
#include "fs/Path.hxx"
29
#include "fs/NarrowPath.hxx"
30
#include "pcm/AudioFormat.hxx"
31 32
#include "util/OptionDef.hxx"
#include "util/OptionParser.hxx"
33
#include "util/PrintException.hxx"
34
#include "Log.hxx"
35
#include "LogBackend.hxx"
36

37
#include <cassert>
38 39
#include <stdexcept>

40
#include <unistd.h>
41
#include <stdlib.h>
42
#include <stdio.h>
43

44 45 46 47
struct CommandLine {
	const char *decoder = nullptr;
	const char *uri = nullptr;

48
	FromNarrowPath config_path;
49 50

	bool verbose = false;
51 52

	SongTime seek_where{};
53 54 55 56 57
};

enum Option {
	OPTION_CONFIG,
	OPTION_VERBOSE,
58
	OPTION_SEEK,
59 60 61 62 63
};

static constexpr OptionDef option_defs[] = {
	{"config", 0, true, "Load a MPD configuration file"},
	{"verbose", 'v', false, "Verbose logging"},
64
	{"seek", 0, true, "Seek to this position"},
65 66 67 68 69 70 71 72 73 74 75
};

static CommandLine
ParseCommandLine(int argc, char **argv)
{
	CommandLine c;

	OptionParser option_parser(option_defs, argc, argv);
	while (auto o = option_parser.Next()) {
		switch (Option(o.index)) {
		case OPTION_CONFIG:
76
			c.config_path = o.value;
77 78 79 80 81
			break;

		case OPTION_VERBOSE:
			c.verbose = true;
			break;
82 83 84 85

		case OPTION_SEEK:
			c.seek_where = SongTime::FromS(strtod(o.value, nullptr));
			break;
86 87 88 89 90 91 92 93 94 95 96 97
		}
	}

	auto args = option_parser.GetRemaining();
	if (args.size != 2)
		throw std::runtime_error("Usage: run_decoder [--verbose] [--config=FILE] DECODER URI");

	c.decoder = args[0];
	c.uri = args[1];
	return c;
}

98
class GlobalInit {
99
	const ConfigData config;
100
	EventThread io_thread;
101
	const ScopeInputPluginsInit input_plugins_init;
102
	const ScopeDecoderPluginsInit decoder_plugins_init;
103 104

public:
105
	explicit GlobalInit(Path config_path)
106
		:config(AutoLoadConfigFile(config_path)),
107 108
		 input_plugins_init(config, io_thread.GetEventLoop()),
		 decoder_plugins_init(config)
109
	{
110
		io_thread.Start();
111 112 113
	}
};

114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
class MyDecoderClient final : public DumpDecoderClient {
	SongTime seek_where;

	unsigned sample_rate;

	bool seekable, seek_error = false;

public:
	explicit MyDecoderClient(SongTime _seek_where) noexcept
		:seek_where(_seek_where) {}

	void Finish() {
		if (!IsInitialized())
			throw "Unrecognized file";

		if (seek_error)
			throw "Seek error";

		if (seek_where != SongTime{}) {
			if (!seekable)
				throw "Not seekable";

			throw "Did not seek";
		}
	}

	/* virtual methods from DecoderClient */
	void Ready(AudioFormat audio_format,
		   bool _seekable, SignedSongTime duration) noexcept override {
		assert(!IsInitialized());

		DumpDecoderClient::Ready(audio_format, _seekable, duration);
		sample_rate = audio_format.sample_rate;
		seekable = _seekable;
	}

	DecoderCommand GetCommand() noexcept override {
		assert(IsInitialized());

		if (seek_where != SongTime{}) {
			if (!seekable)
				return DecoderCommand::STOP;

			return DecoderCommand::SEEK;
		} else if (seek_error)
			return DecoderCommand::STOP;
		else
			return DumpDecoderClient::GetCommand();
	}

	void CommandFinished() noexcept override {
		assert(!seek_error);

		if (seek_where != SongTime{})
			seek_where = {};
		else
			DumpDecoderClient::CommandFinished();
	}

	SongTime GetSeekTime() noexcept override {
		assert(seek_where != SongTime{});

		return seek_where;
	}

	uint64_t GetSeekFrame() noexcept override {
		assert(seek_where != SongTime{});

		return GetSeekTime().ToScale<uint64_t>(sample_rate);
	}

	void SeekError() noexcept override {
		assert(seek_where != SongTime{});

		seek_error = true;
		seek_where = {};
	}
};

193
int main(int argc, char **argv)
194
try {
195
	const auto c = ParseCommandLine(argc, argv);
196

197 198
	SetLogThreshold(c.verbose ? LogLevel::DEBUG : LogLevel::INFO);
	const GlobalInit init(c.config_path);
199

200
	const DecoderPlugin *plugin = decoder_plugin_from_name(c.decoder);
201
	if (plugin == nullptr) {
202
		fprintf(stderr, "No such decoder: %s\n", c.decoder);
203
		return EXIT_FAILURE;
204 205
	}

206
	MyDecoderClient client(c.seek_where);
207 208 209 210 211 212
	if (plugin->SupportsUri(c.uri)) {
		try {
			plugin->UriDecode(client, c.uri);
		} catch (StopDecoder) {
		}
	} else if (plugin->file_decode != nullptr) {
213
		try {
214
			plugin->FileDecode(client, FromNarrowPath(c.uri));
215 216
		} catch (StopDecoder) {
		}
217
	} else if (plugin->stream_decode != nullptr) {
218
		auto is = InputStream::OpenReady(c.uri, client.mutex);
219 220 221 222
		try {
			plugin->StreamDecode(client, *is);
		} catch (StopDecoder) {
		}
223
	} else {
224 225
		fprintf(stderr, "Decoder plugin is not usable\n");
		return EXIT_FAILURE;
226 227
	}

228
	client.Finish();
229

230
	return EXIT_SUCCESS;
231 232
} catch (...) {
	PrintException(std::current_exception());
233
	return EXIT_FAILURE;
234
}