~ubuntu-branches/ubuntu/wily/aegisub/wily-proposed

« back to all changes in this revision

Viewing changes to src/video_context.cpp

  • Committer: Package Import Robot
  • Author(s): Sebastian Reichel, Pascal De Vuyst, Juan Picca, Sebastian Reichel
  • Date: 2015-08-04 21:40:50 UTC
  • mfrom: (5.1.1 sid)
  • Revision ID: package-import@ubuntu.com-20150804214050-y2aghm9vdksoc8t7
Tags: 3.2.2+dfsg-1
[ Pascal De Vuyst ]
* Fix Typo in package description (Closes: #739219)

[ Juan Picca ]
* Add patch to fix reproducible build (Closes: #789728)

[ Sebastian Reichel ]
* New upstream release
 - remove vendor directory from orig tarball
* Update Debian Standards Version to 3.9.6

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
// Copyright (c) 2005-2007, Rodrigo Braz Monteiro
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 video_context.cpp
31
 
/// @brief Keep track of loaded video
32
 
/// @ingroup video
33
 
///
34
 
 
35
 
#include "config.h"
36
 
 
37
 
#include "video_context.h"
38
 
 
39
 
#include "ass_dialogue.h"
40
 
#include "ass_file.h"
41
 
#include "ass_time.h"
42
 
#include "audio_controller.h"
43
 
#include "compat.h"
44
 
#include "include/aegisub/context.h"
45
 
#include "include/aegisub/video_provider.h"
46
 
#include "mkv_wrap.h"
47
 
#include "options.h"
48
 
#include "selection_controller.h"
49
 
#include "subs_controller.h"
50
 
#include "time_range.h"
51
 
#include "threaded_frame_source.h"
52
 
#include "utils.h"
53
 
#include "video_frame.h"
54
 
 
55
 
#include <libaegisub/fs.h>
56
 
#include <libaegisub/keyframe.h>
57
 
#include <libaegisub/path.h>
58
 
 
59
 
#include <wx/msgdlg.h>
60
 
 
61
 
VideoContext::VideoContext()
62
 
: playback(this)
63
 
, playAudioOnStep(OPT_GET("Audio/Plays When Stepping Video"))
64
 
{
65
 
        Bind(EVT_VIDEO_ERROR, &VideoContext::OnVideoError, this);
66
 
        Bind(EVT_SUBTITLES_ERROR, &VideoContext::OnSubtitlesError, this);
67
 
        Bind(wxEVT_TIMER, &VideoContext::OnPlayTimer, this);
68
 
 
69
 
        OPT_SUB("Subtitle/Provider", &VideoContext::Reload, this);
70
 
        OPT_SUB("Video/Provider", &VideoContext::Reload, this);
71
 
 
72
 
        // It would be nice to find a way to move these to the individual providers
73
 
        OPT_SUB("Provider/Avisynth/Allow Ancient", &VideoContext::Reload, this);
74
 
        OPT_SUB("Provider/Avisynth/Memory Max", &VideoContext::Reload, this);
75
 
 
76
 
        OPT_SUB("Provider/Video/FFmpegSource/Decoding Threads", &VideoContext::Reload, this);
77
 
        OPT_SUB("Provider/Video/FFmpegSource/Unsafe Seeking", &VideoContext::Reload, this);
78
 
        OPT_SUB("Video/Force BT.601", &VideoContext::Reload, this);
79
 
}
80
 
 
81
 
VideoContext::~VideoContext () {
82
 
}
83
 
 
84
 
VideoContext *VideoContext::Get() {
85
 
        static VideoContext instance;
86
 
        return &instance;
87
 
}
88
 
 
89
 
void VideoContext::Reset() {
90
 
        config::path->SetToken("?video", "");
91
 
 
92
 
        // Remove video data
93
 
        Stop();
94
 
        frame_n = 0;
95
 
 
96
 
        // Clean up video data
97
 
        video_filename.clear();
98
 
 
99
 
        // Remove provider
100
 
        provider.reset();
101
 
        video_provider = nullptr;
102
 
 
103
 
        keyframes.clear();
104
 
        keyframes_filename.clear();
105
 
        video_fps = agi::vfr::Framerate();
106
 
        KeyframesOpen(keyframes);
107
 
        if (!ovr_fps.IsLoaded()) TimecodesOpen(video_fps);
108
 
}
109
 
 
110
 
void VideoContext::SetContext(agi::Context *context) {
111
 
        this->context = context;
112
 
        context->ass->AddCommitListener(&VideoContext::OnSubtitlesCommit, this);
113
 
        context->subsController->AddFileSaveListener(&VideoContext::OnSubtitlesSave, this);
114
 
}
115
 
 
116
 
void VideoContext::SetVideo(const agi::fs::path &filename) {
117
 
        Reset();
118
 
        if (filename.empty()) {
119
 
                VideoOpen();
120
 
                return;
121
 
        }
122
 
 
123
 
        bool commit_subs = false;
124
 
        try {
125
 
                provider.reset(new ThreadedFrameSource(filename, context->ass->GetScriptInfo("YCbCr Matrix"), this));
126
 
                video_provider = provider->GetVideoProvider();
127
 
                video_filename = filename;
128
 
 
129
 
                // Check that the script resolution matches the video resolution
130
 
                int sx = context->ass->GetScriptInfoAsInt("PlayResX");
131
 
                int sy = context->ass->GetScriptInfoAsInt("PlayResY");
132
 
                int vx = GetWidth();
133
 
                int vy = GetHeight();
134
 
 
135
 
                // If the script resolution hasn't been set at all just force it to the
136
 
                // video resolution
137
 
                if (sx == 0 && sy == 0) {
138
 
                        context->ass->SetScriptInfo("PlayResX", std::to_string(vx));
139
 
                        context->ass->SetScriptInfo("PlayResY", std::to_string(vy));
140
 
                        commit_subs = true;
141
 
                }
142
 
                // If it has been set to something other than a multiple of the video
143
 
                // resolution, ask the user if they want it to be fixed
144
 
                else if (sx % vx != 0 || sy % vy != 0) {
145
 
                        switch (OPT_GET("Video/Check Script Res")->GetInt()) {
146
 
                        case 1: // Ask to change on mismatch
147
 
                                if (wxYES != wxMessageBox(
148
 
                                        wxString::Format(_("The resolution of the loaded video and the resolution specified for the subtitles don't match.\n\nVideo resolution:\t%d x %d\nScript resolution:\t%d x %d\n\nChange subtitles resolution to match video?"), vx, vy, sx, sy),
149
 
                                        _("Resolution mismatch"),
150
 
                                        wxYES_NO | wxCENTER,
151
 
                                        context->parent))
152
 
 
153
 
                                        break;
154
 
                                // Fallthrough to case 2
155
 
                        case 2: // Always change script res
156
 
                                context->ass->SetScriptInfo("PlayResX", std::to_string(vx));
157
 
                                context->ass->SetScriptInfo("PlayResY", std::to_string(vy));
158
 
                                commit_subs = true;
159
 
                                break;
160
 
                        default: // Never change
161
 
                                break;
162
 
                        }
163
 
                }
164
 
 
165
 
                keyframes = video_provider->GetKeyFrames();
166
 
 
167
 
                // Set frame rate
168
 
                video_fps = video_provider->GetFPS();
169
 
                if (ovr_fps.IsLoaded()) {
170
 
                        int ovr = wxMessageBox(_("You already have timecodes loaded. Would you like to replace them with timecodes from the video file?"), _("Replace timecodes?"), wxYES_NO | wxICON_QUESTION);
171
 
                        if (ovr == wxYES) {
172
 
                                ovr_fps = agi::vfr::Framerate();
173
 
                                timecodes_filename.clear();
174
 
                        }
175
 
                }
176
 
 
177
 
                // Set aspect ratio
178
 
                double dar = video_provider->GetDAR();
179
 
                if (dar > 0)
180
 
                        SetAspectRatio(dar);
181
 
 
182
 
                // Set filename
183
 
                config::mru->Add("Video", filename);
184
 
                config::path->SetToken("?video", filename);
185
 
 
186
 
                // Show warning
187
 
                std::string warning = video_provider->GetWarning();
188
 
                if (!warning.empty())
189
 
                        wxMessageBox(to_wx(warning), "Warning", wxICON_WARNING | wxOK);
190
 
 
191
 
                has_subtitles = false;
192
 
                if (agi::fs::HasExtension(filename, "mkv"))
193
 
                        has_subtitles = MatroskaWrapper::HasSubtitles(filename);
194
 
 
195
 
                provider->LoadSubtitles(context->ass);
196
 
                VideoOpen();
197
 
                KeyframesOpen(keyframes);
198
 
                TimecodesOpen(FPS());
199
 
        }
200
 
        catch (agi::UserCancelException const&) { }
201
 
        catch (agi::fs::FileSystemError const& err) {
202
 
                config::mru->Remove("Video", filename);
203
 
                wxMessageBox(to_wx(err.GetMessage()), "Error setting video", wxOK | wxICON_ERROR | wxCENTER);
204
 
        }
205
 
        catch (VideoProviderError const& err) {
206
 
                wxMessageBox(to_wx(err.GetMessage()), "Error setting video", wxOK | wxICON_ERROR | wxCENTER);
207
 
        }
208
 
 
209
 
        if (commit_subs)
210
 
                context->ass->Commit(_("change script resolution"), AssFile::COMMIT_SCRIPTINFO);
211
 
        else
212
 
                JumpToFrame(0);
213
 
}
214
 
 
215
 
void VideoContext::Reload() {
216
 
        if (IsLoaded()) {
217
 
                int frame = frame_n;
218
 
                SetVideo(agi::fs::path(video_filename)); // explicitly copy videoFile since it's cleared in SetVideo
219
 
                JumpToFrame(frame);
220
 
        }
221
 
}
222
 
 
223
 
void VideoContext::OnSubtitlesCommit(int type, std::set<const AssEntry *> const& changed) {
224
 
        if (!IsLoaded()) return;
225
 
 
226
 
        if (changed.empty() || no_amend)
227
 
                provider->LoadSubtitles(context->ass);
228
 
        else
229
 
                provider->UpdateSubtitles(context->ass, changed);
230
 
        if (!IsPlaying())
231
 
                GetFrameAsync(frame_n);
232
 
 
233
 
        no_amend = false;
234
 
}
235
 
 
236
 
void VideoContext::OnSubtitlesSave() {
237
 
        no_amend = true;
238
 
 
239
 
        context->ass->SetScriptInfo("VFR File", config::path->MakeRelative(GetTimecodesName(), "?script").generic_string());
240
 
        context->ass->SetScriptInfo("Keyframes File", config::path->MakeRelative(GetKeyFramesName(), "?script").generic_string());
241
 
 
242
 
        if (!IsLoaded()) {
243
 
                context->ass->SetScriptInfo("Video File", "");
244
 
                context->ass->SaveUIState("Video Aspect Ratio", "");
245
 
                context->ass->SaveUIState("Video Position", "");
246
 
                return;
247
 
        }
248
 
 
249
 
        std::string ar;
250
 
        if (ar_type == AspectRatio::Custom)
251
 
                ar = "c" + std::to_string(ar_value);
252
 
        else
253
 
                ar = std::to_string((int)ar_type);
254
 
 
255
 
        context->ass->SetScriptInfo("Video File", config::path->MakeRelative(video_filename, "?script").generic_string());
256
 
        auto matrix = video_provider->GetColorSpace();
257
 
        if (!matrix.empty())
258
 
                context->ass->SetScriptInfo("YCbCr Matrix", matrix);
259
 
        context->ass->SaveUIState("Video Aspect Ratio", ar);
260
 
        context->ass->SaveUIState("Video Position", std::to_string(frame_n));
261
 
}
262
 
 
263
 
void VideoContext::JumpToFrame(int n) {
264
 
        if (!IsLoaded()) return;
265
 
 
266
 
        bool was_playing = IsPlaying();
267
 
        if (was_playing)
268
 
                Stop();
269
 
 
270
 
        frame_n = mid(0, n, GetLength() - 1);
271
 
 
272
 
        GetFrameAsync(frame_n);
273
 
        Seek(frame_n);
274
 
 
275
 
        if (was_playing)
276
 
                Play();
277
 
}
278
 
 
279
 
void VideoContext::JumpToTime(int ms, agi::vfr::Time end) {
280
 
        JumpToFrame(FrameAtTime(ms, end));
281
 
}
282
 
 
283
 
void VideoContext::GetFrameAsync(int n) {
284
 
        provider->RequestFrame(n, TimeAtFrame(n));
285
 
}
286
 
 
287
 
std::shared_ptr<VideoFrame> VideoContext::GetFrame(int n, bool raw) {
288
 
        return provider->GetFrame(n, TimeAtFrame(n), raw);
289
 
}
290
 
 
291
 
int VideoContext::GetWidth() const { return video_provider->GetWidth(); }
292
 
int VideoContext::GetHeight() const { return video_provider->GetHeight(); }
293
 
int VideoContext::GetLength() const { return video_provider->GetFrameCount(); }
294
 
 
295
 
void VideoContext::NextFrame() {
296
 
        if (!video_provider || IsPlaying() || frame_n == video_provider->GetFrameCount())
297
 
                return;
298
 
 
299
 
        JumpToFrame(frame_n + 1);
300
 
        if (playAudioOnStep->GetBool())
301
 
                context->audioController->PlayRange(TimeRange(TimeAtFrame(frame_n - 1), TimeAtFrame(frame_n)));
302
 
}
303
 
 
304
 
void VideoContext::PrevFrame() {
305
 
        if (!video_provider || IsPlaying() || frame_n == 0)
306
 
                return;
307
 
 
308
 
        JumpToFrame(frame_n - 1);
309
 
        if (playAudioOnStep->GetBool())
310
 
                context->audioController->PlayRange(TimeRange(TimeAtFrame(frame_n), TimeAtFrame(frame_n + 1)));
311
 
}
312
 
 
313
 
void VideoContext::Play() {
314
 
        if (IsPlaying()) {
315
 
                Stop();
316
 
                return;
317
 
        }
318
 
 
319
 
        if (!IsLoaded()) return;
320
 
 
321
 
        start_ms = TimeAtFrame(frame_n);
322
 
        end_frame = GetLength() - 1;
323
 
 
324
 
        context->audioController->PlayToEnd(start_ms);
325
 
 
326
 
        playback_start_time = std::chrono::steady_clock::now();
327
 
        playback.Start(10);
328
 
}
329
 
 
330
 
void VideoContext::PlayLine() {
331
 
        Stop();
332
 
 
333
 
        AssDialogue *curline = context->selectionController->GetActiveLine();
334
 
        if (!curline) return;
335
 
 
336
 
        context->audioController->PlayRange(TimeRange(curline->Start, curline->End));
337
 
 
338
 
        // Round-trip conversion to convert start to exact
339
 
        int startFrame = FrameAtTime(context->selectionController->GetActiveLine()->Start, agi::vfr::START);
340
 
        start_ms = TimeAtFrame(startFrame);
341
 
        end_frame = FrameAtTime(context->selectionController->GetActiveLine()->End, agi::vfr::END) + 1;
342
 
 
343
 
        JumpToFrame(startFrame);
344
 
 
345
 
        playback_start_time = std::chrono::steady_clock::now();
346
 
        playback.Start(10);
347
 
}
348
 
 
349
 
void VideoContext::Stop() {
350
 
        if (IsPlaying()) {
351
 
                playback.Stop();
352
 
                context->audioController->Stop();
353
 
        }
354
 
}
355
 
 
356
 
void VideoContext::OnPlayTimer(wxTimerEvent &) {
357
 
        using namespace std::chrono;
358
 
        int next_frame = FrameAtTime(start_ms + duration_cast<milliseconds>(steady_clock::now() - playback_start_time).count());
359
 
        if (next_frame == frame_n) return;
360
 
 
361
 
        if (next_frame >= end_frame)
362
 
                Stop();
363
 
        else {
364
 
                frame_n = next_frame;
365
 
                GetFrameAsync(frame_n);
366
 
                Seek(frame_n);
367
 
        }
368
 
}
369
 
 
370
 
double VideoContext::GetARFromType(AspectRatio type) const {
371
 
        switch (type) {
372
 
                case AspectRatio::Default: return (double)GetWidth()/(double)GetHeight();
373
 
                case AspectRatio::Fullscreen: return 4.0/3.0;
374
 
                case AspectRatio::Widescreen: return 16.0/9.0;
375
 
                case AspectRatio::Cinematic: return 2.35;
376
 
        }
377
 
        throw agi::InternalError("Bad AR type", nullptr);
378
 
}
379
 
 
380
 
void VideoContext::SetAspectRatio(double value) {
381
 
        ar_type = AspectRatio::Custom;
382
 
        ar_value = mid(.5, value, 5.);
383
 
        ARChange(ar_type, ar_value);
384
 
}
385
 
 
386
 
void VideoContext::SetAspectRatio(AspectRatio type) {
387
 
        ar_value = mid(.5, GetARFromType(type), 5.);
388
 
        ar_type = type;
389
 
        ARChange(ar_type, ar_value);
390
 
}
391
 
 
392
 
void VideoContext::LoadKeyframes(agi::fs::path const& filename) {
393
 
        if (filename == keyframes_filename || filename.empty()) return;
394
 
        try {
395
 
                keyframes = agi::keyframe::Load(filename);
396
 
                keyframes_filename = filename;
397
 
                KeyframesOpen(keyframes);
398
 
                config::mru->Add("Keyframes", filename);
399
 
        }
400
 
        catch (agi::keyframe::Error const& err) {
401
 
                wxMessageBox(to_wx(err.GetMessage()), "Error opening keyframes file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
402
 
                config::mru->Remove("Keyframes", filename);
403
 
        }
404
 
        catch (agi::fs::FileSystemError const& err) {
405
 
                wxMessageBox(to_wx(err.GetMessage()), "Error opening keyframes file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
406
 
                config::mru->Remove("Keyframes", filename);
407
 
        }
408
 
}
409
 
 
410
 
void VideoContext::SaveKeyframes(agi::fs::path const& filename) {
411
 
        agi::keyframe::Save(filename, GetKeyFrames());
412
 
        config::mru->Add("Keyframes", filename);
413
 
}
414
 
 
415
 
void VideoContext::CloseKeyframes() {
416
 
        keyframes_filename.clear();
417
 
        if (video_provider)
418
 
                keyframes = video_provider->GetKeyFrames();
419
 
        else
420
 
                keyframes.clear();
421
 
        KeyframesOpen(keyframes);
422
 
}
423
 
 
424
 
void VideoContext::LoadTimecodes(agi::fs::path const& filename) {
425
 
        if (filename == timecodes_filename || filename.empty()) return;
426
 
        try {
427
 
                ovr_fps = agi::vfr::Framerate(filename);
428
 
                timecodes_filename = filename;
429
 
                config::mru->Add("Timecodes", filename);
430
 
                OnSubtitlesCommit(0, std::set<const AssEntry*>());
431
 
                TimecodesOpen(ovr_fps);
432
 
        }
433
 
        catch (agi::fs::FileSystemError const& err) {
434
 
                wxMessageBox(to_wx(err.GetMessage()), "Error opening timecodes file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
435
 
                config::mru->Remove("Timecodes", filename);
436
 
        }
437
 
        catch (const agi::vfr::Error& e) {
438
 
                wxLogError("Timecode file parse error: %s", to_wx(e.GetMessage()));
439
 
                config::mru->Remove("Timecodes", filename);
440
 
        }
441
 
}
442
 
void VideoContext::SaveTimecodes(agi::fs::path const& filename) {
443
 
        try {
444
 
                FPS().Save(filename, IsLoaded() ? GetLength() : -1);
445
 
                config::mru->Add("Timecodes", filename);
446
 
        }
447
 
        catch (agi::fs::FileSystemError const& err) {
448
 
                wxMessageBox(to_wx(err.GetMessage()), "Error saving timecodes", wxOK | wxICON_ERROR | wxCENTER, context->parent);
449
 
        }
450
 
}
451
 
void VideoContext::CloseTimecodes() {
452
 
        ovr_fps = agi::vfr::Framerate();
453
 
        timecodes_filename.clear();
454
 
    OnSubtitlesCommit(0, std::set<const AssEntry*>());
455
 
        TimecodesOpen(video_fps);
456
 
}
457
 
 
458
 
int VideoContext::TimeAtFrame(int frame, agi::vfr::Time type) const {
459
 
        return (ovr_fps.IsLoaded() ? ovr_fps : video_fps).TimeAtFrame(frame, type);
460
 
}
461
 
 
462
 
int VideoContext::FrameAtTime(int time, agi::vfr::Time type) const {
463
 
        return (ovr_fps.IsLoaded() ? ovr_fps : video_fps).FrameAtTime(time, type);
464
 
}
465
 
 
466
 
void VideoContext::OnVideoError(VideoProviderErrorEvent const& err) {
467
 
        wxLogError(
468
 
                "Failed seeking video. The video file may be corrupt or incomplete.\n"
469
 
                "Error message reported: %s",
470
 
                to_wx(err.GetMessage()));
471
 
}
472
 
void VideoContext::OnSubtitlesError(SubtitlesProviderErrorEvent const& err) {
473
 
        wxLogError(
474
 
                "Failed rendering subtitles. Error message reported: %s",
475
 
                to_wx(err.GetMessage()));
476
 
}