You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

289 lines
8.5KB

  1. /*
  2. Write points to SubRip subtitle file (for video geotagging)
  3. Copyright (C) 2010 Michael von Glasow, michael @t vonglasow d.t com
  4. Copyright (C) 2014 Gleb Smirnoff, glebius @t FreeBSD d.t org
  5. This program is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 2 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program; if not, write to the Free Software
  15. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
  16. */
  17. #include "defs.h"
  18. #include <stdio.h> /* for gmtime */
  19. #define MYNAME "subrip"
  20. static char* opt_videotime;
  21. static char* opt_gpstime;
  22. static char* opt_gpsdate;
  23. static char* opt_format;
  24. static time_t time_offset;
  25. static int stnum;
  26. static gbfile* fout;
  27. static const Waypoint* prevwpp;
  28. static double vspeed;
  29. static double gradient;
  30. /* internal helper functions */
  31. static time_t
  32. sync_time(time_t arg_gpstime, char* arg_videotime)
  33. {
  34. static time_t videotime_t;
  35. static struct tm* ptm_video;
  36. static time_t result;
  37. videotime_t = 0;
  38. ptm_video = gmtime(&videotime_t);
  39. if (arg_videotime) {
  40. sscanf(arg_videotime, "%2d%2d%2d", &ptm_video->tm_hour, &ptm_video->tm_min, &ptm_video->tm_sec);
  41. }
  42. videotime_t = mkgmtime(ptm_video);
  43. result = (arg_gpstime - videotime_t);
  44. return result;
  45. }
  46. static void
  47. subrip_prevwp_pr(const Waypoint* waypointp)
  48. {
  49. QDateTime startdtime, enddtime;
  50. QTime starttime, endtime;
  51. /* Now that we have the next waypoint, we can write out the subtitle for
  52. * the previous one.
  53. */
  54. /* If this condition is not true, the waypoint is before the beginning of
  55. * the video and will be ignored
  56. */
  57. if (prevwpp->GetCreationTime().toTime_t() < time_offset) {
  58. return;
  59. }
  60. gbfprintf(fout, "%d\n", stnum++);
  61. /* Writes start and end time for subtitle display to file. */
  62. startdtime = prevwpp->GetCreationTime().addSecs(-time_offset);
  63. if (!waypointp) {
  64. enddtime = startdtime.addSecs(1);
  65. } else {
  66. enddtime = waypointp->GetCreationTime().addSecs(-time_offset);
  67. }
  68. starttime = startdtime.toUTC().time();
  69. endtime = enddtime.toUTC().time();
  70. gbfprintf(fout, "%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d\n",
  71. starttime.hour(), starttime.minute(), starttime.second(), starttime.msec(),
  72. endtime.hour(), endtime.minute(), endtime.second(), endtime.msec());
  73. for (char* c = opt_format; *c != '\0' ; c++) {
  74. char fmt;
  75. switch (*c) {
  76. case '%':
  77. fmt = *++c;
  78. is_fatal(fmt == '\0', "No character after %% in subrip format");
  79. switch (fmt) {
  80. case 's':
  81. if WAYPT_HAS(prevwpp, speed)
  82. gbfprintf(fout, "%2.1f", MPS_TO_KPH(prevwpp->speed));
  83. else
  84. gbfprintf(fout, "--.-");
  85. break;
  86. case 'e':
  87. if (prevwpp->altitude != unknown_alt)
  88. gbfprintf(fout, "%4d", (int)prevwpp->altitude);
  89. else
  90. gbfprintf(fout, " -");
  91. break;
  92. case 'v':
  93. gbfprintf(fout, "%2.2f", vspeed);
  94. break;
  95. case 'g':
  96. gbfprintf(fout, "%2.1f%%", gradient);
  97. break;
  98. case 't':
  99. {
  100. QTime t = prevwpp->GetCreationTime().toUTC().time();
  101. gbfprintf(fout, "%02d:%02d:%02d", t.hour(), t.minute(), t.second());
  102. break;
  103. }
  104. case 'l':
  105. // The +.00005 is for rounding.
  106. gbfprintf(fout, "Lat=%0.5lf Lon=%0.5lf",
  107. prevwpp->latitude+.000005, prevwpp->longitude+.000005);
  108. break;
  109. case 'c':
  110. if (prevwpp->cadence != 0)
  111. gbfprintf(fout, "%3u", prevwpp->cadence);
  112. else
  113. gbfprintf(fout, " -");
  114. break;
  115. case 'h':
  116. if (prevwpp->heartrate != 0)
  117. gbfprintf(fout, "%3u", prevwpp->heartrate);
  118. else
  119. gbfprintf(fout, " -");
  120. break;
  121. }
  122. break;
  123. case '\\':
  124. fmt = *++c;
  125. is_fatal(fmt == '\0', "No character after \\ in subrip format");
  126. switch (fmt) {
  127. case 'n':
  128. gbfprintf(fout, "\n");
  129. break;
  130. }
  131. break;
  132. default:
  133. gbfwrite(c, 1, 1, fout);
  134. }
  135. }
  136. gbfprintf(fout, "\n\n");
  137. }
  138. /* callback functions */
  139. static void
  140. subrip_trkpt_pr(const Waypoint* waypointp)
  141. {
  142. /*
  143. * To determine the duration of the subtitle, we need the timestamp of the
  144. * associated waypoint plus that of the following one.
  145. * Since we get waypoints one at a time, the only way is to store one and
  146. * defer processing until we get the next one.
  147. *
  148. * To determine vertical speed we need to have not only previous waypoint,
  149. * but also pre-previous, so we calculate vspeed right before forgetting
  150. * the previous.
  151. */
  152. if ((stnum == 1) && (time_offset == 0))
  153. /*
  154. * esoteric bug: GPS tracks created on Jan 1, 1970 at midnight would cause
  155. * undesirable behavior here. But if you run into this problem, I assume
  156. * you are capable of time-travel as well as inventing a high-tech system
  157. * some 20 years before the rest of mankind does, so finding a prettier
  158. * way of solving this should be trivial to you :-)
  159. */
  160. {
  161. time_offset = sync_time(waypointp->GetCreationTime().toTime_t(), opt_videotime);
  162. }
  163. if (prevwpp) {
  164. subrip_prevwp_pr(waypointp);
  165. vspeed = waypt_vertical_speed(waypointp, prevwpp);
  166. gradient = waypt_gradient(waypointp, prevwpp);
  167. }
  168. prevwpp = waypointp;
  169. }
  170. /* global callback (exported) functions */
  171. static void
  172. subrip_wr_init(const QString& fname)
  173. {
  174. time_t gpstime_t;
  175. struct tm* ptm_gps;
  176. stnum = 1;
  177. time_offset = 0;
  178. prevwpp = NULL;
  179. vspeed = 0;
  180. gradient = 0;
  181. if ((opt_gpstime != NULL) && (opt_gpsdate != NULL)) {
  182. time(&gpstime_t);
  183. ptm_gps = gmtime(&gpstime_t);
  184. if (opt_gpstime) {
  185. sscanf(opt_gpstime, "%2d%2d%2d", &ptm_gps->tm_hour, &ptm_gps->tm_min, &ptm_gps->tm_sec);
  186. }
  187. if (opt_gpsdate) {
  188. sscanf(opt_gpsdate, "%4d%2d%2d", &ptm_gps->tm_year, &ptm_gps->tm_mon, &ptm_gps->tm_mday);
  189. /*
  190. * Don't ask me why we need to do this nonsense, but it seems to be necessary:
  191. * Years are two-digit since this was fashionable in the mid-1900s.
  192. * For dates after 2000, just add 100 to the year.
  193. * Months are zero-based (0 is January), but days are one-based.
  194. * Makes sense, eh?
  195. * Btw: correct dates will result in incorrect timestamps and you'll
  196. * never figure out why. Suppose that's to confuse the Russians,
  197. * given that the system was developed during the Cold War. But that
  198. * is true for most of Unix.
  199. * Make a difference - contribute to ReactOS.
  200. */
  201. ptm_gps->tm_year-=1900;
  202. ptm_gps->tm_mon--;
  203. }
  204. gpstime_t = mkgmtime(ptm_gps);
  205. time_offset = sync_time(gpstime_t, opt_videotime);
  206. }
  207. fout = gbfopen(fname, "wb", MYNAME);
  208. }
  209. static void
  210. subrip_wr_deinit(void)
  211. {
  212. gbfclose(fout);
  213. }
  214. static void
  215. subrip_write(void)
  216. {
  217. track_disp_all(NULL, NULL, subrip_trkpt_pr);
  218. /*
  219. * Due to the necessary hack, one waypoint is still in memory (unless we
  220. * did not get any waypoints). Check if there is one and, if so, write it.
  221. */
  222. if (prevwpp) {
  223. subrip_prevwp_pr(NULL);
  224. }
  225. }
  226. /* arguments: definitions of format-specific arguments */
  227. arglist_t subrip_args[] = {
  228. // FIXME: document that gps_date and gps_time must be specified together or they will both be ignored and the timestamp of the first trackpoint will be used.
  229. {"video_time", &opt_videotime, "Video position for which exact GPS time is known (hhmmss, default is 0:00:00)", 0, ARGTYPE_STRING, ARG_NOMINMAX },
  230. {"gps_time", &opt_gpstime, "GPS time at position video_time (hhmmss, default is first timestamp of track)", 0, ARGTYPE_STRING, ARG_NOMINMAX },
  231. {"gps_date", &opt_gpsdate, "GPS date at position video_time (hhmmss, default is first timestamp of track)", 0, ARGTYPE_STRING, ARG_NOMINMAX },
  232. {"format", &opt_format, "Format for subtitles", "%s km/h %e m\\n%t %l", ARGTYPE_STRING, ARG_NOMINMAX },
  233. ARG_TERMINATOR
  234. };
  235. /* manifest: capabilities of this module, pointers to exported functions and others */
  236. ff_vecs_t subrip_vecs = {
  237. ff_type_file,
  238. { ff_cap_none, ff_cap_write, ff_cap_none }, // waypoints, track, route; for now, we just do tracks
  239. NULL,
  240. subrip_wr_init,
  241. NULL,
  242. subrip_wr_deinit,
  243. NULL,
  244. subrip_write,
  245. NULL,
  246. subrip_args,
  247. CET_CHARSET_ASCII, 0
  248. };