1 // Copyright (c) 2008-2009, Karl Blomster
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 //
7 //   * Redistributions of source code must retain the above copyright notice,
8 //     this list of conditions and the following disclaimer.
9 //   * Redistributions in binary form must reproduce the above copyright notice,
10 //     this list of conditions and the following disclaimer in the documentation
11 //     and/or other materials provided with the distribution.
12 //   * Neither the name of the Aegisub Group nor the names of its contributors
13 //     may be used to endorse or promote products derived from this software
14 //     without specific prior written permission.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 // POSSIBILITY OF SUCH DAMAGE.
27 //
28 // Aegisub Project http://www.aegisub.org/
29 
30 /// @file ffmpegsource_common.cpp
31 /// @brief Shared code for ffms video and audio providers
32 /// @ingroup video_input audio_input ffms
33 ///
34 
35 #ifdef WITH_FFMS2
36 #include "ffmpegsource_common.h"
37 
38 #include "compat.h"
39 #include "format.h"
40 #include "options.h"
41 #include "utils.h"
42 
43 #include <libaegisub/background_runner.h>
44 #include <libaegisub/fs.h>
45 #include <libaegisub/path.h>
46 
47 #include <boost/algorithm/string/case_conv.hpp>
48 #include <boost/crc.hpp>
49 #include <boost/filesystem/path.hpp>
50 #include <wx/intl.h>
51 #include <wx/choicdlg.h>
52 
53 #ifdef _WIN32
54 #include <objbase.h>
55 
deinit_com(bool)56 static void deinit_com(bool) {
57 	CoUninitialize();
58 }
59 #else
deinit_com(bool)60 static void deinit_com(bool) { }
61 #endif
62 
FFmpegSourceProvider(agi::BackgroundRunner * br)63 FFmpegSourceProvider::FFmpegSourceProvider(agi::BackgroundRunner *br)
64 : COMInited(false, deinit_com)
65 , br(br)
66 {
67 #ifdef _WIN32
68 	HRESULT res = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
69 	if (SUCCEEDED(res))
70 		COMInited = true;
71 	else if (res != RPC_E_CHANGED_MODE)
72 		throw agi::EnvironmentError("COM initialization failure");
73 #endif
74 
75 	// initialize ffmpegsource
76 	FFMS_Init(0, 1);
77 }
78 
79 /// @brief Does indexing of a source file
80 /// @param Indexer		A pointer to the indexer object representing the file to be indexed
81 /// @param CacheName    The filename of the output index file
82 /// @param Trackmask    A binary mask of the track numbers to index
DoIndexing(FFMS_Indexer * Indexer,agi::fs::path const & CacheName,TrackSelection Track,FFMS_IndexErrorHandling IndexEH)83 FFMS_Index *FFmpegSourceProvider::DoIndexing(FFMS_Indexer *Indexer,
84 	                                         agi::fs::path const& CacheName,
85 	                                         TrackSelection Track,
86 	                                         FFMS_IndexErrorHandling IndexEH) {
87 	char FFMSErrMsg[1024];
88 	FFMS_ErrorInfo ErrInfo;
89 	ErrInfo.Buffer		= FFMSErrMsg;
90 	ErrInfo.BufferSize	= sizeof(FFMSErrMsg);
91 	ErrInfo.ErrorType	= FFMS_ERROR_SUCCESS;
92 	ErrInfo.SubType		= FFMS_ERROR_SUCCESS;
93 
94 	// index all audio tracks
95 	FFMS_Index *Index;
96 	br->Run([&](agi::ProgressSink *ps) {
97 		ps->SetTitle(from_wx(_("Indexing")));
98 		ps->SetMessage(from_wx(_("Reading timecodes and frame/sample data")));
99 		TIndexCallback callback = [](int64_t Current, int64_t Total, void *Private) -> int {
100 			auto ps = static_cast<agi::ProgressSink *>(Private);
101 			ps->SetProgress(Current, Total);
102 			return ps->IsCancelled();
103 		};
104 #if FFMS_VERSION >= ((2 << 24) | (21 << 16) | (0 << 8) | 0)
105 		if (Track == TrackSelection::All)
106 			FFMS_TrackTypeIndexSettings(Indexer, FFMS_TYPE_AUDIO, 1, 0);
107 		else if (Track != TrackSelection::None)
108 			FFMS_TrackIndexSettings(Indexer, static_cast<int>(Track), 1, 0);
109 		FFMS_SetProgressCallback(Indexer, callback, ps);
110 		Index = FFMS_DoIndexing2(Indexer, IndexEH, &ErrInfo);
111 #else
112 		int Trackmask = 0;
113 		if (Track == TrackSelection::All)
114 			Trackmask = std::numeric_limits<int>::max();
115 		else if (Track != TrackSelection::None)
116 			Trackmask = 1 << static_cast<int>(Track);
117 		Index = FFMS_DoIndexing(Indexer, Trackmask, 0,
118 			nullptr, nullptr, IndexEH, callback, ps, &ErrInfo);
119 #endif
120 	});
121 
122 	if (Index == nullptr)
123 		throw agi::EnvironmentError(std::string("Failed to index: ") + ErrInfo.Buffer);
124 
125 	// write index to disk for later use
126 	FFMS_WriteIndex(CacheName.string().c_str(), Index, &ErrInfo);
127 
128 	return Index;
129 }
130 
131 /// @brief Finds all tracks of the given type and return their track numbers and respective codec names
132 /// @param Indexer	The indexer object representing the source file
133 /// @param Type		The track type to look for
134 /// @return			Returns a std::map with the track numbers as keys and the codec names as values.
GetTracksOfType(FFMS_Indexer * Indexer,FFMS_TrackType Type)135 std::map<int, std::string> FFmpegSourceProvider::GetTracksOfType(FFMS_Indexer *Indexer, FFMS_TrackType Type) {
136 	std::map<int,std::string> TrackList;
137 	int NumTracks = FFMS_GetNumTracksI(Indexer);
138 
139 	// older versions of ffms2 can't index audio tracks past 31
140 #if FFMS_VERSION < ((2 << 24) | (21 << 16) | (0 << 8) | 0)
141 	if (Type == FFMS_TYPE_AUDIO)
142 		NumTracks = std::min(NumTracks, std::numeric_limits<int>::digits);
143 #endif
144 
145 	for (int i=0; i<NumTracks; i++) {
146 		if (FFMS_GetTrackTypeI(Indexer, i) == Type) {
147 			if (auto CodecName = FFMS_GetCodecNameI(Indexer, i))
148 				TrackList[i] = CodecName;
149 		}
150 	}
151 	return TrackList;
152 }
153 
154 FFmpegSourceProvider::TrackSelection
AskForTrackSelection(const std::map<int,std::string> & TrackList,FFMS_TrackType Type)155 FFmpegSourceProvider::AskForTrackSelection(const std::map<int, std::string> &TrackList,
156                                            FFMS_TrackType Type) {
157 	std::vector<int> TrackNumbers;
158 	wxArrayString Choices;
159 
160 	for (auto const& track : TrackList) {
161 		Choices.Add(agi::format(_("Track %02d: %s"), track.first, to_wx(track.second)));
162 		TrackNumbers.push_back(track.first);
163 	}
164 
165 	int Choice = wxGetSingleChoiceIndex(
166 		Type == FFMS_TYPE_VIDEO ? _("Multiple video tracks detected, please choose the one you wish to load:") : _("Multiple audio tracks detected, please choose the one you wish to load:"),
167 		Type == FFMS_TYPE_VIDEO ? _("Choose video track") : _("Choose audio track"),
168 		Choices);
169 
170 	if (Choice < 0)
171 		return TrackSelection::None;
172 	return static_cast<TrackSelection>(TrackNumbers[Choice]);
173 }
174 
175 /// @brief Set ffms2 log level according to setting in config.dat
SetLogLevel()176 void FFmpegSourceProvider::SetLogLevel() {
177 	auto LogLevel = OPT_GET("Provider/FFmpegSource/Log Level")->GetString();
178 	boost::to_lower(LogLevel);
179 
180 	if (LogLevel == "panic")
181 		FFMS_SetLogLevel(FFMS_LOG_PANIC);
182 	else if (LogLevel == "fatal")
183 		FFMS_SetLogLevel(FFMS_LOG_FATAL);
184 	else if (LogLevel == "error")
185 		FFMS_SetLogLevel(FFMS_LOG_ERROR);
186 	else if (LogLevel == "warning")
187 		FFMS_SetLogLevel(FFMS_LOG_WARNING);
188 	else if (LogLevel == "info")
189 		FFMS_SetLogLevel(FFMS_LOG_INFO);
190 	else if (LogLevel == "verbose")
191 		FFMS_SetLogLevel(FFMS_LOG_VERBOSE);
192 	else if (LogLevel == "debug")
193 		FFMS_SetLogLevel(FFMS_LOG_DEBUG);
194 	else
195 		FFMS_SetLogLevel(FFMS_LOG_QUIET);
196 }
197 
GetErrorHandlingMode()198 FFMS_IndexErrorHandling FFmpegSourceProvider::GetErrorHandlingMode() {
199 	auto Mode = OPT_GET("Provider/Audio/FFmpegSource/Decode Error Handling")->GetString();
200 	boost::to_lower(Mode);
201 
202 	if (Mode == "ignore")
203 		return FFMS_IEH_IGNORE;
204 	if (Mode == "clear")
205 		return FFMS_IEH_CLEAR_TRACK;
206 	if (Mode == "stop")
207 		return FFMS_IEH_STOP_TRACK;
208 	if (Mode == "abort")
209 		return FFMS_IEH_ABORT;
210 	return FFMS_IEH_STOP_TRACK; // questionable default?
211 }
212 
213 /// @brief	Generates an unique name for the ffms2 index file and prepares the cache folder if it doesn't exist
214 /// @param filename	The name of the source file
215 /// @return			Returns the generated filename.
GetCacheFilename(agi::fs::path const & filename)216 agi::fs::path FFmpegSourceProvider::GetCacheFilename(agi::fs::path const& filename) {
217 	// Get the size of the file to be hashed
218 	uintmax_t len = agi::fs::Size(filename);
219 
220 	// Get the hash of the filename
221 	boost::crc_32_type hash;
222 	hash.process_bytes(filename.string().c_str(), filename.string().size());
223 
224 	// Generate the filename
225 	auto result = config::path->Decode("?local/ffms2cache/" + std::to_string(hash.checksum()) + "_" + std::to_string(len) + "_" + std::to_string(agi::fs::ModifiedTime(filename)) + ".ffindex");
226 
227 	// Ensure that folder exists
228 	agi::fs::CreateDirectory(result.parent_path());
229 
230 	return result;
231 }
232 
CleanCache()233 void FFmpegSourceProvider::CleanCache() {
234 	::CleanCache(config::path->Decode("?local/ffms2cache/"),
235 		"*.ffindex",
236 		OPT_GET("Provider/FFmpegSource/Cache/Size")->GetInt(),
237 		OPT_GET("Provider/FFmpegSource/Cache/Files")->GetInt());
238 }
239 
240 #endif // WITH_FFMS2
241