diff --git a/src/media/media_recorder.cpp b/src/media/media_recorder.cpp
index fa00536fc63fcf854970d795a1e089a73c7e1186..a45b9f232079aaa95d01046dd62911f46a87ac05 100644
--- a/src/media/media_recorder.cpp
+++ b/src/media/media_recorder.cpp
@@ -39,6 +39,20 @@ namespace ring {
 
 static constexpr auto FRAME_DEQUEUE_INTERVAL = std::chrono::milliseconds(200);
 
+static std::string
+replaceAll(const std::string& str, const std::string& from, const std::string& to)
+{
+    if (from.empty())
+        return str;
+    std::string copy(str);
+    size_t startPos = 0;
+    while ((startPos = str.find(from, startPos)) != std::string::npos) {
+        copy.replace(startPos, from.length(), to);
+        startPos += to.length();
+    }
+    return copy;
+}
+
 MediaRecorder::MediaRecorder()
     : loop_([]{ return true;},
             [this]{ process(); },
@@ -224,19 +238,21 @@ MediaRecorder::initRecord()
 
     std::map<std::string, std::string> encoderOptions;
 
+    std::stringstream timestampString;
+    timestampString << std::put_time(&startTime_, "%Y-%m-%d %H:%M:%S");
+
     if (title_.empty()) {
         std::stringstream ss;
-        ss << "Ring recording at " << std::put_time(&startTime_, "%Y-%m-%d %H:%M:%S");
+        ss << "Conversation at %TIMESTAMP";
         title_ = ss.str();
     }
+    title_ = replaceAll(title_, "%TIMESTAMP", timestampString.str());
     encoderOptions["title"] = title_;
 
     if (description_.empty()) {
-        std::stringstream ss;
-        ss << "Recorded at " << std::put_time(&startTime_, "%Y-%m-%d %H:%M:%S")
-            << " with Ring https://ring.cx";
-        description_ = ss.str();
+        description_ = "Recorded with Ring https://ring.cx";
     }
+    description_ = replaceAll(description_, "%TIMESTAMP", timestampString.str());
     encoderOptions["description"] = description_;
 
     videoFilter_.reset();
diff --git a/src/media/media_recorder.h b/src/media/media_recorder.h
index 9a46fc409d4ace2d8cf7961ae0f1e2b7c5d14cbf..eee2288e14acb11ea180ba1d32abf237781018c3 100644
--- a/src/media/media_recorder.h
+++ b/src/media/media_recorder.h
@@ -50,8 +50,9 @@ class MediaRecorder {
 
         void audioOnly(bool audioOnly);
 
-        // default title is: "Ring recording at %Y-%m-%d %H:%M:%S"
-        // default description is: "Recorded at %Y-%m-%d %H:%M:%S with Ring https://ring.cx"
+        // replaces %TIMESTAMP with time at start of recording
+        // default title: "Conversation at %Y-%m-%d %H:%M:%S"
+        // default description: "Recorded with Ring https://ring.cx"
         void setMetadata(const std::string& title, const std::string& desc);
 
         [[deprecated("use setPath to set full recording path")]]
diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp
index 848536ad0b3e847f110043d3f59cdf0c2a8dac80..06c973f19b8c9652b96a8631fec29403cadd97bc 100644
--- a/src/sip/sipcall.cpp
+++ b/src/sip/sipcall.cpp
@@ -1155,7 +1155,8 @@ SIPCall::toggleRecording()
     const bool startRecording = Call::toggleRecording();
     if (startRecording) {
         std::stringstream ss;
-        ss << "Ring call between " << getSIPAccount().getUserUri() << " and " << peerUri_;
+        ss << "Conversation at %TIMESTAMP between "
+            << getSIPAccount().getUserUri() << " and " << peerUri_;
         recorder_->setMetadata(ss.str(), ""); // use default description
         if (avformatrtp_)
             avformatrtp_->startRecorder(recorder_);