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