diff --git a/gst/isomp4/atoms.c b/gst/isomp4/atoms.c
index e0ec513..cbd2245 100644
--- a/gst/isomp4/atoms.c
+++ b/gst/isomp4/atoms.c
@@ -398,6 +398,99 @@ atom_edts_free (AtomEDTS * edts)
 }
 
 static void
+atom_tcmi_init (AtomTCMI * tcmi)
+{
+  guint8 flags[3] = { 0, 0, 0 };
+
+  atom_full_init (&tcmi->header, FOURCC_tcmi, 0, 0, 0, flags);
+}
+
+static void
+atom_tcmi_clear (AtomTCMI * tcmi)
+{
+  atom_full_clear (&tcmi->header);
+  tcmi->text_font = 0;
+  tcmi->text_face = 0;
+  tcmi->text_size = 0;
+  tcmi->padding = 0;
+  tcmi->text_color[0] = 0;
+  tcmi->text_color[1] = 0;
+  tcmi->text_color[2] = 0;
+  tcmi->bg_color[0] = 0;
+  tcmi->bg_color[1] = 0;
+  tcmi->bg_color[2] = 0;
+  if (tcmi->font_name) {
+    g_free (tcmi->font_name);
+    tcmi->font_name = NULL;
+  }
+}
+
+static void
+atom_tmcd_init (AtomTMCD * tmcd)
+{
+  atom_header_set (&tmcd->header, FOURCC_tmcd, 0, 0);
+  atom_tcmi_init (&tmcd->tcmi);
+}
+
+static void
+atom_tmcd_clear (AtomTMCD * tmcd)
+{
+  atom_clear (&tmcd->header);
+  atom_tcmi_clear (&tmcd->tcmi);
+}
+
+static void
+atom_gmin_init (AtomGMIN * gmin)
+{
+  guint8 flags[3] = { 0, 0, 0 };
+
+  atom_full_init (&gmin->header, FOURCC_gmin, 0, 0, 0, flags);
+}
+
+static void
+atom_gmin_clear (AtomGMIN * gmin)
+{
+  atom_full_clear (&gmin->header);
+  gmin->graphics_mode = 0;
+  gmin->opcolor[0] = 0;
+  gmin->opcolor[1] = 0;
+  gmin->opcolor[2] = 0;
+  gmin->balance = 0;
+  gmin->reserved = 0;
+}
+
+static void
+atom_gmhd_init (AtomGMHD * gmhd)
+{
+  atom_header_set (&gmhd->header, FOURCC_gmhd, 0, 0);
+  atom_gmin_init (&gmhd->gmin);
+  atom_tmcd_init (&gmhd->tmcd);
+}
+
+static void
+atom_gmhd_clear (AtomGMHD * gmhd)
+{
+  atom_clear (&gmhd->header);
+  atom_gmin_clear (&gmhd->gmin);
+  atom_tmcd_clear (&gmhd->tmcd);
+}
+
+static AtomGMHD *
+atom_gmhd_new (void)
+{
+  AtomGMHD *gmhd = g_new0 (AtomGMHD, 1);
+  atom_gmhd_init (gmhd);
+  return gmhd;
+}
+
+static void
+atom_gmhd_free (AtomGMHD * gmhd)
+{
+  atom_gmhd_clear (gmhd);
+  g_free (gmhd);
+}
+
+static void
 atom_sample_entry_init (SampleTableEntry * se, guint32 type)
 {
   atom_header_set (&se->header, type, 0, 0);
@@ -452,6 +545,33 @@ sample_entry_mp4a_free (SampleTableEntryMP4A * mp4a)
 }
 
 static void
+sample_entry_tmcd_init (SampleTableEntryTMCD * tmcd)
+{
+  atom_sample_entry_init (&tmcd->se, FOURCC_tmcd);
+
+  tmcd->tc_flags = 0;
+  tmcd->timescale = 0;
+  tmcd->frame_duration = 0;
+  tmcd->n_frames = 0;
+
+  tmcd->name.string_length = 0;
+  tmcd->name.language_code = 0;
+  if (tmcd->name.name) {
+    g_free (tmcd->name.name);
+    tmcd->name.name = NULL;
+  }
+}
+
+static SampleTableEntryTMCD *
+sample_entry_tmcd_new (void)
+{
+  SampleTableEntryTMCD *tmcd = g_new0 (SampleTableEntryTMCD, 1);
+
+  sample_entry_tmcd_init (tmcd);
+  return tmcd;
+}
+
+static void
 sample_entry_mp4v_init (SampleTableEntryMP4V * mp4v, AtomsContext * context)
 {
   atom_sample_entry_init (&mp4v->se, FOURCC_mp4v);
@@ -934,6 +1054,7 @@ atom_minf_init (AtomMINF * minf, AtomsContext * context)
   minf->vmhd = NULL;
   minf->smhd = NULL;
   minf->hmhd = NULL;
+  minf->gmhd = NULL;
 
   if (context->flavor == ATOMS_TREE_FLAVOR_MOV) {
     minf->hdlr = atom_hdlr_new (context);
@@ -961,6 +1082,10 @@ atom_minf_clear_handlers (AtomMINF * minf)
     atom_hmhd_free (minf->hmhd);
     minf->hmhd = NULL;
   }
+  if (minf->gmhd) {
+    atom_gmhd_free (minf->gmhd);
+    minf->gmhd = NULL;
+  }
 }
 
 static void
@@ -1651,6 +1776,89 @@ atom_hmhd_copy_data (AtomHMHD * hmhd, guint8 ** buffer, guint64 * size,
   return original_offset - *offset;
 }
 
+static guint64
+atom_tcmi_copy_data (AtomTCMI * tcmi, guint8 ** buffer, guint64 * size,
+    guint64 * offset)
+{
+  guint64 original_offset = *offset;
+
+  if (!atom_full_copy_data (&tcmi->header, buffer, size, offset)) {
+    return 0;
+  }
+  prop_copy_uint16 (tcmi->text_font, buffer, size, offset);
+  prop_copy_uint16 (tcmi->text_face, buffer, size, offset);
+  prop_copy_uint16 (tcmi->text_size, buffer, size, offset);
+  prop_copy_uint16 (tcmi->text_color[0], buffer, size, offset);
+  prop_copy_uint16 (tcmi->text_color[1], buffer, size, offset);
+  prop_copy_uint16 (tcmi->text_color[2], buffer, size, offset);
+  prop_copy_uint16 (tcmi->bg_color[0], buffer, size, offset);
+  prop_copy_uint16 (tcmi->bg_color[1], buffer, size, offset);
+  prop_copy_uint16 (tcmi->bg_color[2], buffer, size, offset);
+  prop_copy_uint16 (tcmi->padding, buffer, size, offset);
+  prop_copy_size_string ((guint8 *)tcmi->font_name, strlen (tcmi->font_name), buffer, size, offset);
+
+  atom_write_size (buffer, size, offset, original_offset);
+  return original_offset - *offset;
+}
+
+static guint64
+atom_tmcd_copy_data (AtomTMCD * tmcd, guint8 ** buffer, guint64 * size,
+    guint64 * offset)
+{
+  guint64 original_offset = *offset;
+
+  if (!atom_copy_data (&tmcd->header, buffer, size, offset)) {
+    return 0;
+  }
+  if (!atom_tcmi_copy_data (&tmcd->tcmi, buffer, size, offset)) {
+    return 0;
+  }
+
+  atom_write_size (buffer, size, offset, original_offset);
+  return original_offset - *offset;
+}
+
+static guint64
+atom_gmin_copy_data (AtomGMIN * gmin, guint8 ** buffer, guint64 * size,
+    guint64 * offset)
+{
+  guint64 original_offset = *offset;
+
+  if (!atom_full_copy_data (&gmin->header, buffer, size, offset)) {
+    return 0;
+  }
+  prop_copy_uint16 (gmin->graphics_mode, buffer, size, offset);
+  prop_copy_uint16 (gmin->opcolor[0], buffer, size, offset);
+  prop_copy_uint16 (gmin->opcolor[1], buffer, size, offset);
+  prop_copy_uint16 (gmin->opcolor[2], buffer, size, offset);
+  prop_copy_uint8 (gmin->balance, buffer, size, offset);
+  /* reserved */
+  prop_copy_uint8 (0, buffer, size, offset);
+
+  atom_write_size (buffer, size, offset, original_offset);
+  return original_offset - *offset;
+}
+
+static guint64
+atom_gmhd_copy_data (AtomGMHD * gmhd, guint8 ** buffer, guint64 * size,
+    guint64 * offset)
+{
+  guint64 original_offset = *offset;
+
+  if (!atom_copy_data (&gmhd->header, buffer, size, offset)) {
+    return 0;
+  }
+  if (!atom_gmin_copy_data (&gmhd->gmin, buffer, size, offset)) {
+    return 0;
+  }
+  if (!atom_tmcd_copy_data (&gmhd->tmcd, buffer, size, offset)) {
+    return 0;
+  }
+
+  atom_write_size (buffer, size, offset, original_offset);
+  return original_offset - *offset;
+}
+
 static gboolean
 atom_url_same_file_flag (AtomURL * url)
 {
@@ -1884,6 +2092,35 @@ sample_entry_tx3g_copy_data (SampleTableEntryTX3G * tx3g, guint8 ** buffer,
   return *offset - original_offset;
 }
 
+static guint64
+sample_entry_tmcd_copy_data (SampleTableEntryTMCD * tmcd, guint8 ** buffer,
+    guint64 * size, guint64 * offset)
+{
+  guint64 original_offset = *offset;
+
+  if (!atom_sample_entry_copy_data (&tmcd->se, buffer, size, offset)) {
+    return 0;
+  }
+
+  prop_copy_uint32 (tmcd->tc_flags, buffer, size, offset);
+  prop_copy_uint32 (tmcd->timescale, buffer, size, offset);
+  prop_copy_uint32 (tmcd->frame_duration, buffer, size, offset);
+  prop_copy_uint8 (tmcd->n_frames, buffer, size, offset);
+
+  {
+    Atom atom;
+
+    atom_header_set (&atom, FOURCC_name, 0, 0);
+    atom_copy_data (&atom, buffer, size, offset);
+    prop_copy_uint8 (tmcd->name.string_length, buffer, size, offset);
+    prop_copy_uint8 (tmcd->name.language_code, buffer, size, offset);
+    prop_copy_fixed_size_string ((guint8 *)tmcd->name.name, tmcd->name.string_length, buffer, size, offset);
+  }
+
+  atom_write_size (buffer, size, offset, original_offset);
+  return *offset - original_offset;
+}
+
 guint64
 atom_stsz_copy_data (AtomSTSZ * stsz, guint8 ** buffer, guint64 * size,
     guint64 * offset)
@@ -2074,6 +2311,11 @@ atom_stsd_copy_data (AtomSTSD * stsd, guint8 ** buffer, guint64 * size,
                   walker->data, buffer, size, offset)) {
             return 0;
           }
+        } else if (se->kind == TIMECODE) {
+          if (!sample_entry_tmcd_copy_data ((SampleTableEntryTMCD *)
+                  walker->data, buffer, size, offset)) {
+            return 0;
+          }
         } else {
           if (!atom_hint_sample_entry_copy_data (
                   (AtomHintSampleEntry *) walker->data, buffer, size, offset)) {
@@ -2205,6 +2447,10 @@ atom_minf_copy_data (AtomMINF * minf, guint8 ** buffer, guint64 * size,
     if (!atom_hmhd_copy_data (minf->hmhd, buffer, size, offset)) {
       return 0;
     }
+  } else if (minf->gmhd) {
+    if (!atom_gmhd_copy_data (minf->gmhd, buffer, size, offset)) {
+      return 0;
+    }
   }
 
   if (minf->hdlr) {
@@ -3323,6 +3569,29 @@ atom_trak_add_audio_entry (AtomTRAK * trak, AtomsContext * context,
   return mp4a;
 }
 
+static SampleTableEntryTMCD *
+atom_trak_add_timecode_entry (AtomTRAK * trak, AtomsContext * context, GstVideoTimeCode * tc)
+{
+  AtomSTSD *stsd = &trak->mdia.minf.stbl.stsd;
+  SampleTableEntryTMCD *tmcd = sample_entry_tmcd_new ();
+
+  tmcd->se.kind = TIMECODE;
+  tmcd->se.data_reference_index = 1;
+  tmcd->tc_flags = TC_24H_MAX;
+  if (tc->config.flags &= GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME)
+    tmcd->tc_flags |= TC_DROP_FRAME;
+  tmcd->name.string_length = 8;
+  tmcd->name.language_code = 0;
+  tmcd->name.name = g_strdup ("tapename");
+  tmcd->timescale = tc->config.fps_n;
+  tmcd->frame_duration = tc->config.fps_d;
+  tmcd->n_frames = 20;
+
+  stsd->entries = g_list_prepend (stsd->entries, tmcd);
+  stsd->n_entries++;
+  return tmcd;
+}
+
 static SampleTableEntryMP4V *
 atom_trak_add_video_entry (AtomTRAK * trak, AtomsContext * context,
     guint32 type)
@@ -3467,6 +3736,36 @@ atom_trak_set_audio_type (AtomTRAK * trak, AtomsContext * context,
   return ste;
 }
 
+SampleTableEntryTMCD *
+atom_trak_set_timecode_type (AtomTRAK * trak, AtomsContext * context, guint32 timescale, GstVideoTimeCode * tc)
+{
+  SampleTableEntryTMCD *ste;
+  AtomGMHD *gmhd = trak->mdia.minf.gmhd;
+
+  if (context->flavor != ATOMS_TREE_FLAVOR_MOV) {
+    return NULL;
+  }
+
+  ste = atom_trak_add_timecode_entry (trak, context, tc);
+
+  gmhd = atom_gmhd_new ();
+  gmhd->gmin.graphics_mode = 0x0040;
+  gmhd->gmin.opcolor[0] = 0x8000;
+  gmhd->gmin.opcolor[1] = 0x8000;
+  gmhd->gmin.opcolor[2] = 0x8000;
+  gmhd->tmcd.tcmi.text_size = 12;
+  gmhd->tmcd.tcmi.font_name = g_strdup ("Chicago"); /* Pascal string */
+
+  trak->is_video = FALSE;
+  trak->is_h264 = FALSE;
+
+  //@@@ move this into qtmux
+  /* values for delta and size are hardcoded, sync is false because no STSS */
+  //atom_trak_add_samples (trak, 1, 1, 4, OFFSET, FALSE, 0);
+
+  return ste;
+}
+
 static AtomInfo *
 build_pasp_extension (gint par_width, gint par_height)
 {
diff --git a/gst/isomp4/atoms.h b/gst/isomp4/atoms.h
index b105a4d..6e2f84d 100644
--- a/gst/isomp4/atoms.h
+++ b/gst/isomp4/atoms.h
@@ -45,6 +45,7 @@
 
 #include <glib.h>
 #include <string.h>
+#include <gst/video/video.h>
 
 #include "descriptors.h"
 #include "properties.h"
@@ -281,6 +282,46 @@ typedef struct _AtomHMHD
   guint32 sliding_avg_bitrate;
 } AtomHMHD;
 
+typedef struct _AtomTCMI
+{
+  AtomFull header;
+
+  guint16 text_font;
+  guint16 text_face;
+  guint16 text_size;
+  guint16 padding;
+  guint16 text_color[3];
+  guint16 bg_color[3];
+  gchar *font_name;
+} AtomTCMI;
+
+typedef struct _AtomTMCD
+{
+  Atom header;
+
+  AtomTCMI tcmi;
+} AtomTMCD;
+
+typedef struct _AtomGMIN
+{
+  AtomFull header;
+
+  guint16 graphics_mode;
+  guint16 opcolor[3];
+  guint8 balance;
+  guint8 reserved;
+
+} AtomGMIN;
+
+typedef struct _AtomGMHD
+{
+  Atom header;
+
+  AtomGMIN gmin;
+  AtomTMCD tmcd;
+
+} AtomGMHD;
+
 typedef struct _AtomURL
 {
   AtomFull header;
@@ -342,6 +383,7 @@ typedef enum _SampleEntryKind
   AUDIO,
   VIDEO,
   SUBTITLE,
+  TIMECODE
 } SampleEntryKind;
 
 typedef struct _SampleTableEntry
@@ -415,6 +457,28 @@ typedef struct _SampleTableEntryMP4A
   GList *extension_atoms;
 } SampleTableEntryMP4A;
 
+typedef struct _AtomNAME
+{
+  Atom header;
+
+  guint8 string_length;
+  guint8 language_code;
+  gchar *name;
+} AtomNAME;
+
+typedef struct _SampleTableEntryTMCD
+{
+  SampleTableEntry se;
+
+  guint32 tc_flags;
+  guint32 timescale;
+  guint32 frame_duration;
+  guint8 n_frames;
+
+  AtomNAME name;
+
+} SampleTableEntryTMCD;
+
 typedef struct _SampleTableEntryTX3G
 {
   SampleTableEntry se;
@@ -514,6 +578,7 @@ typedef struct _AtomMINF
   AtomVMHD *vmhd;
   AtomSMHD *smhd;
   AtomHMHD *hmhd;
+  AtomGMHD *gmhd;
 
   AtomHDLR *hdlr;
   AtomDINF dinf;
@@ -616,6 +681,15 @@ enum TfFlags
   TF_DEFAULT_BASE_IS_MOOF     = 0x020000  /* default-base-is-moof */
 };
 
+/* Timecode flags */
+enum TcFlags
+{
+  TC_DROP_FRAME = 0x0001,   /* Drop-frame timecode */
+  TC_24H_MAX = 0x0002,      /* Whether the timecode wraps after 24 hours */
+  TC_NEGATIVE_OK = 0x0004,  /* Whether negative time values are OK */
+  TC_COUNTER = 0x0008       /* Whether the time value corresponds to a tape counter value */
+};
+
 typedef struct _AtomTRAK
 {
   Atom header;
@@ -937,6 +1011,9 @@ SampleTableEntryMP4V * atom_trak_set_video_type (AtomTRAK * trak, AtomsContext *
 SampleTableEntryTX3G * atom_trak_set_subtitle_type (AtomTRAK * trak, AtomsContext * context,
                                SubtitleSampleEntry * entry);
 
+SampleTableEntryTMCD *
+atom_trak_set_timecode_type (AtomTRAK * trak, AtomsContext * context, guint32 timescale, GstVideoTimeCode * tc);
+
 void atom_trak_update_bitrates (AtomTRAK * trak, guint32 avg_bitrate,
                                 guint32 max_bitrate);
 
diff --git a/gst/isomp4/fourcc.h b/gst/isomp4/fourcc.h
index 5bb43e9..9e7408b 100644
--- a/gst/isomp4/fourcc.h
+++ b/gst/isomp4/fourcc.h
@@ -215,7 +215,9 @@ G_BEGIN_DECLS
 #define FOURCC_subp     GST_MAKE_FOURCC('s','u','b','p')
 #define FOURCC_subt     GST_MAKE_FOURCC('s','u','b','t')
 #define FOURCC_text     GST_MAKE_FOURCC('t','e','x','t')
+#define FOURCC_tcmi     GST_MAKE_FOURCC('t','c','m','i')
 #define FOURCC_tkhd     GST_MAKE_FOURCC('t','k','h','d')
+#define FOURCC_tmcd     GST_MAKE_FOURCC('t','m','c','d')
 #define FOURCC_tmpo     GST_MAKE_FOURCC('t','m','p','o')
 #define FOURCC_trak     GST_MAKE_FOURCC('t','r','a','k')
 #define FOURCC_tref     GST_MAKE_FOURCC('t','r','e','f')
