diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index b3242bcef..d9612fce8 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -1055,7 +1055,7 @@ void GMainWindow::BootGame(const QString& filename) {
         game_list->hide();
         game_list_placeholder->hide();
     }
-    status_bar_update_timer.start(2000);
+    status_bar_update_timer.start(1000);
 
     if (UISettings::values.hide_mouse) {
         mouse_hide_timer.start();
@@ -1165,6 +1165,7 @@ void GMainWindow::ShutdownGame() {
     // Disable status bar updates
     status_bar_update_timer.stop();
     message_label->setVisible(false);
+    message_label_used_for_movie = false;
     emu_speed_label->setVisible(false);
     game_fps_label->setVisible(false);
     emu_frametime_label->setVisible(false);
@@ -1982,6 +1983,23 @@ void GMainWindow::UpdateStatusBar() {
         return;
     }
 
+    // Update movie status
+    const u64 current = Core::Movie::GetInstance().GetCurrentInputIndex();
+    const u64 total = Core::Movie::GetInstance().GetTotalInputCount();
+    if (Core::Movie::GetInstance().IsRecordingInput()) {
+        message_label->setText(tr("Recording %1").arg(current));
+        message_label->setVisible(true);
+        message_label_used_for_movie = true;
+    } else if (Core::Movie::GetInstance().IsPlayingInput()) {
+        message_label->setText(tr("Playing %1 / %2").arg(current).arg(total));
+        message_label->setVisible(true);
+        message_label_used_for_movie = true;
+    } else if (message_label_used_for_movie) { // Clear the label if movie was just closed
+        message_label->setText(QString{});
+        message_label->setVisible(false);
+        message_label_used_for_movie = false;
+    }
+
     auto results = Core::System::GetInstance().GetAndResetPerfStats();
 
     if (Settings::values.use_frame_limit_alternate) {
@@ -2093,6 +2111,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
             emu_thread->SetRunning(true);
             message_label->setText(status_message);
             message_label->setVisible(true);
+            message_label_used_for_movie = false;
         }
     }
 }
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index eff5ea7b5..99451d308 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -248,6 +248,7 @@ private:
     QLabel* game_fps_label = nullptr;
     QLabel* emu_frametime_label = nullptr;
     QTimer status_bar_update_timer;
+    bool message_label_used_for_movie = false;
 
     MultiplayerState* multiplayer_state = nullptr;
     std::unique_ptr<Config> config;
diff --git a/src/core/movie.cpp b/src/core/movie.cpp
index 51746e744..1e27b45f2 100644
--- a/src/core/movie.cpp
+++ b/src/core/movie.cpp
@@ -129,6 +129,22 @@ struct CTMHeader {
 static_assert(sizeof(CTMHeader) == 256, "CTMHeader should be 256 bytes");
 #pragma pack(pop)
 
+static u64 GetInputCount(const std::vector<u8>& input) {
+    u64 input_count = 0;
+    for (std::size_t pos = 0; pos < input.size(); pos += sizeof(ControllerState)) {
+        if (input.size() < pos + sizeof(ControllerState)) {
+            break;
+        }
+
+        ControllerState state;
+        std::memcpy(&state, input.data() + pos, sizeof(ControllerState));
+        if (state.type == ControllerStateType::PadAndCircle) {
+            input_count++;
+        }
+    }
+    return input_count;
+}
+
 template <class Archive>
 void Movie::serialize(Archive& ar, const unsigned int file_version) {
     // Only serialize what's needed to make savestates useful for TAS:
@@ -136,6 +152,10 @@ void Movie::serialize(Archive& ar, const unsigned int file_version) {
     ar& _current_byte;
     current_byte = static_cast<std::size_t>(_current_byte);
 
+    if (file_version > 0) {
+        ar& current_input;
+    }
+
     std::vector<u8> recorded_input_ = recorded_input;
     ar& recorded_input_;
 
@@ -167,6 +187,7 @@ void Movie::serialize(Archive& ar, const unsigned int file_version) {
                     "This savestate was created at a later point and must be loaded in R+W mode");
             }
             play_mode = PlayMode::Playing;
+            total_input = GetInputCount(recorded_input);
         } else {
             recorded_input = std::move(recorded_input_);
             play_mode = PlayMode::Recording;
@@ -184,6 +205,13 @@ bool Movie::IsRecordingInput() const {
     return play_mode == PlayMode::Recording;
 }
 
+u64 Movie::GetCurrentInputIndex() const {
+    return current_input;
+}
+u64 Movie::GetTotalInputCount() const {
+    return total_input;
+}
+
 void Movie::CheckInputEnd() {
     if (current_byte + sizeof(ControllerState) > recorded_input.size()) {
         LOG_INFO(Movie, "Playback finished");
@@ -198,6 +226,7 @@ void Movie::Play(Service::HID::PadState& pad_state, s16& circle_pad_x, s16& circ
     ControllerState s;
     std::memcpy(&s, &recorded_input[current_byte], sizeof(ControllerState));
     current_byte += sizeof(ControllerState);
+    current_input++;
 
     if (s.type != ControllerStateType::PadAndCircle) {
         LOG_ERROR(Movie,
@@ -325,6 +354,8 @@ void Movie::Record(const ControllerState& controller_state) {
 
 void Movie::Record(const Service::HID::PadState& pad_state, const s16& circle_pad_x,
                    const s16& circle_pad_y) {
+    current_input++;
+
     ControllerState s;
     s.type = ControllerStateType::PadAndCircle;
 
@@ -429,22 +460,6 @@ Movie::ValidationResult Movie::ValidateHeader(const CTMHeader& header) const {
     return ValidationResult::OK;
 }
 
-static u64 GetInputCount(const std::vector<u8>& input) {
-    u64 input_count = 0;
-    for (std::size_t pos = 0; pos < input.size(); pos += sizeof(ControllerState)) {
-        if (input.size() < pos + sizeof(ControllerState)) {
-            break;
-        }
-
-        ControllerState state;
-        std::memcpy(&state, input.data() + pos, sizeof(ControllerState));
-        if (state.type == ControllerStateType::PadAndCircle) {
-            input_count++;
-        }
-    }
-    return input_count;
-}
-
 Movie::ValidationResult Movie::ValidateInput(const std::vector<u8>& input,
                                              u64 expected_count) const {
     return GetInputCount(input) == expected_count ? ValidationResult::OK
@@ -507,11 +522,13 @@ void Movie::StartPlayback(const std::string& movie_file) {
             record_movie_author = author.data();
 
             rerecord_count = header.rerecord_count;
+            total_input = header.input_count;
 
             recorded_input.resize(size - sizeof(CTMHeader));
             save_record.ReadArray(recorded_input.data(), recorded_input.size());
 
             current_byte = 0;
+            current_input = 0;
             id = header.id;
 
             LOG_INFO(Movie, "Loaded Movie, ID: {:016X}", id);
@@ -622,6 +639,7 @@ void Movie::Shutdown() {
     recorded_input.resize(0);
     record_movie_file.clear();
     current_byte = 0;
+    current_input = 0;
     init_time = 0;
     id = 0;
 }
diff --git a/src/core/movie.h b/src/core/movie.h
index dcbe8129b..e62dfe5c2 100644
--- a/src/core/movie.h
+++ b/src/core/movie.h
@@ -123,6 +123,9 @@ public:
     bool IsPlayingInput() const;
     bool IsRecordingInput() const;
 
+    u64 GetCurrentInputIndex() const;
+    u64 GetTotalInputCount() const;
+
 private:
     static Movie s_instance;
 
@@ -159,6 +162,9 @@ private:
 
     std::vector<u8> recorded_input;
     std::size_t current_byte = 0;
+    u64 current_input = 0;
+    // Total input count of the current movie being played. Not used for recording.
+    u64 total_input = 0;
 
     u64 id = 0; // ID of the current movie loaded
     u64 init_time;