*/
+/** @file src/stl_binary_writer.cc
+ * @brief Writer for STL binary files.
+ */
+
#include "stl_binary_writer.h"
#include "subtitle.h"
#include "iso6937.h"
+#include "stl_util.h"
#include "compose.hpp"
+#include "sub_assert.h"
#include <boost/locale.hpp>
#include <list>
#include <cmath>
using std::max;
using std::cout;
using boost::locale::conv::utf_to_utf;
+using boost::optional;
using namespace sub;
+/** Arbitrary number which to divide the screen into rows; e.g.
+ * 64 here would mean that there are 64 addressable vertical positions
+ * on the screen, each 1/64th of the screen height tall.
+ *
+ * The magic 23 makes our output agree more closely with
+ * AnnotationEdit, which makes life easier when testing.
+ */
+static int const ROWS = 23;
+
static void
put_string (char* p, string s)
{
static void
put_string (char* p, unsigned int n, string s)
{
- assert (s.length() <= n);
-
+ SUB_ASSERT (s.length() <= n);
+
memcpy (p, s.c_str (), s.length ());
memset (p + s.length(), ' ', n - s.length ());
}
static void
put_int_as_string (char* p, int v, unsigned int n)
{
- std::stringstream s;
+ locked_stringstream s;
/* Be careful to ensure we get no thousands separators */
s.imbue (std::locale::classic ());
s << setw (n) << setfill ('0');
s << v;
- assert (s.str().length() == n);
+ SUB_ASSERT (s.str().length() == n);
put_string (p, s.str ());
}
}
}
+static int
+vertical_position (sub::Line const & line)
+{
+ int vp = 0;
+ if (line.vertical_position.proportional) {
+ switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
+ case TOP_OF_SCREEN:
+ vp = rint (line.vertical_position.proportional.get() * ROWS);
+ break;
+ case CENTRE_OF_SCREEN:
+ vp = rint (line.vertical_position.proportional.get() * ROWS + (ROWS / 2.0));
+ break;
+ case BOTTOM_OF_SCREEN:
+ vp = rint (ROWS - (line.vertical_position.proportional.get() * ROWS));
+ break;
+ default:
+ break;
+ }
+ } else if (line.vertical_position.line) {
+ float const prop = float (line.vertical_position.line.get()) / line.vertical_position.lines.get ();
+ switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
+ case TOP_OF_SCREEN:
+ vp = prop * ROWS;
+ break;
+ case CENTRE_OF_SCREEN:
+ vp = (prop + 0.5) * ROWS;
+ break;
+ case BOTTOM_OF_SCREEN:
+ vp = (1 - prop) * ROWS;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return vp;
+}
+
/** @param language ISO 3-character country code for the language of the subtitles */
void
sub::write_stl_binary (
boost::filesystem::path file_name
)
{
- assert (original_programme_title.size() <= 32);
- assert (original_episode_title.size() <= 32);
- assert (translated_programme_title.size() <= 32);
- assert (translated_episode_title.size() <= 32);
- assert (translator_name.size() <= 32);
- assert (translator_contact_details.size() <= 32);
- assert (creation_date.size() == 6);
- assert (revision_date.size() == 6);
- assert (revision_number <= 99);
- assert (country_of_origin.size() == 3);
- assert (publisher.size() <= 32);
- assert (editor_name.size() <= 32);
- assert (editor_contact_details.size() <= 32);
-
+ SUB_ASSERT (original_programme_title.size() <= 32);
+ SUB_ASSERT (original_episode_title.size() <= 32);
+ SUB_ASSERT (translated_programme_title.size() <= 32);
+ SUB_ASSERT (translated_episode_title.size() <= 32);
+ SUB_ASSERT (translator_name.size() <= 32);
+ SUB_ASSERT (translator_contact_details.size() <= 32);
+ SUB_ASSERT (creation_date.size() == 6);
+ SUB_ASSERT (revision_date.size() == 6);
+ SUB_ASSERT (revision_number <= 99);
+ SUB_ASSERT (country_of_origin.size() == 3);
+ SUB_ASSERT (publisher.size() <= 32);
+ SUB_ASSERT (editor_name.size() <= 32);
+ SUB_ASSERT (editor_contact_details.size() <= 32);
+
char* buffer = new char[1024];
memset (buffer, 0, 1024);
ofstream output (file_name.string().c_str ());
STLBinaryTables tables;
- /* Find the longest subtitle in characters and the number of rows */
+ /* Find the longest subtitle in characters */
int longest = 0;
- set<float> check_top;
- set<float> check_centre;
- set<float> check_bottom;
- set<int> check_rows;
-
for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
int t = 0;
t += k->text.size ();
}
longest = max (longest, t);
-
- if (j->vertical_position.proportional) {
- switch (j->vertical_position.reference.get ()) {
- case TOP:
- check_top.insert (j->vertical_position.proportional.get ());
- break;
- case CENTRE:
- check_centre.insert (j->vertical_position.proportional.get ());
- break;
- case BOTTOM:
- check_bottom.insert (j->vertical_position.proportional.get ());
- break;
- }
- } else {
- check_rows.insert (j->vertical_position.line.get ());
- }
}
}
- int const rows = check_top.size() + check_centre.size() + check_bottom.size() + check_rows.size();
-
/* Code page: 850 */
put_string (buffer + 0, "850");
/* Disk format code */
- put_string (buffer + 3, String::compose ("STL%1.01", rint (frames_per_second)));
+ put_string (buffer + 3, stl_frame_rate_to_dfc (frames_per_second));
/* Display standard code: open subtitling */
put_string (buffer + 11, "0");
/* Character code table: Latin (ISO 6937) */
put_string (buffer + 230, revision_date);
put_int_as_string (buffer + 236, revision_number, 2);
/* TTI blocks */
- put_int_as_string (buffer + 238, subtitles.size (), 5);
+ put_int_as_string (buffer + 238, subtitles.size(), 5);
/* Total number of subtitles */
- put_int_as_string (buffer + 243, subtitles.size (), 5);
+ put_int_as_string (buffer + 243, subtitles.size(), 5);
/* Total number of subtitle groups */
put_string (buffer + 248, "001");
/* Maximum number of displayable characters in any text row */
put_int_as_string (buffer + 251, longest, 2);
/* Maximum number of displayable rows */
- put_int_as_string (buffer + 253, rows, 2);
+ put_int_as_string (buffer + 253, ROWS, 2);
/* Time code status */
put_string (buffer + 255, "1");
/* Start-of-programme time code */
output.write (buffer, 1024);
- int N = 0;
for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
+ /* Find the top vertical position of this subtitle */
+ optional<int> top;
+ for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
+ int const vp = vertical_position (*j);
+ if (!top || vp < top.get ()) {
+ top = vp;
+ }
+ }
+
memset (buffer, 0, 1024);
/* Subtitle group number */
put_int_as_int (buffer + 0, 1, 1);
/* Subtitle number */
- put_int_as_int (buffer + 1, N, 2);
+ put_int_as_int (buffer + 1, 0, 2);
/* Extension block number. Use 0xff here to indicate that it is the last TTI
block in this subtitle "set", as we only ever use one.
*/
/* Cumulative status */
put_int_as_int (buffer + 4, tables.cumulative_status_enum_to_file (CUMULATIVE_STATUS_NOT_CUMULATIVE), 1);
/* Time code in */
- put_int_as_int (buffer + 5, i->from.frame(frames_per_second).hours (), 1);
- put_int_as_int (buffer + 6, i->from.frame(frames_per_second).minutes (), 1);
- put_int_as_int (buffer + 7, i->from.frame(frames_per_second).seconds (), 1);
- put_int_as_int (buffer + 8, i->from.frame(frames_per_second).frames (), 1);
+ put_int_as_int (buffer + 5, i->from.hours(), 1);
+ put_int_as_int (buffer + 6, i->from.minutes(), 1);
+ put_int_as_int (buffer + 7, i->from.seconds(), 1);
+ put_int_as_int (buffer + 8, i->from.frames_at(sub::Rational (frames_per_second * 1000, 1000)), 1);
/* Time code out */
- put_int_as_int (buffer + 9, i->to.frame(frames_per_second).hours (), 1);
- put_int_as_int (buffer + 10, i->to.frame(frames_per_second).minutes (), 1);
- put_int_as_int (buffer + 11, i->to.frame(frames_per_second).seconds (), 1);
- put_int_as_int (buffer + 12, i->to.frame(frames_per_second).frames (), 1);
+ put_int_as_int (buffer + 9, i->to.hours(), 1);
+ put_int_as_int (buffer + 10, i->to.minutes(), 1);
+ put_int_as_int (buffer + 11, i->to.seconds(), 1);
+ put_int_as_int (buffer + 12, i->to.frames_at(sub::Rational (frames_per_second * 1000, 1000)), 1);
/* Vertical position */
- /* XXX */
- put_int_as_int (buffer + 13, 0, 1);
+ put_int_as_int (buffer + 13, top.get(), 1);
+
/* Justification code */
- /* XXX */
- put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_NONE), 1);
+ /* XXX: this assumes the first line has the right value */
+ switch (i->lines.front().horizontal_position) {
+ case LEFT:
+ put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_LEFT), 1);
+ break;
+ case CENTRE:
+ put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_CENTRE), 1);
+ break;
+ case RIGHT:
+ put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_RIGHT), 1);
+ break;
+ }
+
/* Comment flag */
put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
string text;
bool italic = false;
bool underline = false;
-
+ optional<int> last_vp;
+
for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
+
+ /* CR/LF down to this line */
+ int const vp = vertical_position (*j);
+
+ if (last_vp) {
+ for (int i = last_vp.get(); i < vp; ++i) {
+ text += "\x8A";
+ }
+ }
+
+ last_vp = vp;
+
for (list<Block>::const_iterator k = j->blocks.begin(); k != j->blocks.end(); ++k) {
if (k->underline && !underline) {
text += "\x82";
text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k->text));
}
+ }
+
+ /* Turn italic/underline off before the end of this subtitle */
+
+ if (underline) {
+ text += "\x83";
+ }
- text += "\x8A";
+ if (italic) {
+ text += "\x81";
}
if (text.length() > 111) {