1 /* Copyright 2009 Yorba Foundation
3 * This software is licensed under the GNU Lesser General Public License
4 * (version 2.1 or later). See the COPYING file in this distribution.
7 public errordomain MediaError {
11 // I can't find a floating point absolute value function in Vala...
12 public float float_abs(float f) {
18 public bool float_within(double f, double epsilon) {
19 return float_abs((float) f) < epsilon;
22 public int sign(int x) {
25 return x < 0 ? -1 : 1;
28 int stricmp(string str1, string str2) {
29 string temp_str1 = str1.casefold(-1);
30 string temp_str2 = str2.casefold(-1);
32 return temp_str1.collate(temp_str2);
35 // TODO: write this using generics.
36 public string[] copy_array(string[] source) {
37 string[] destination = new string[source.length];
39 foreach (string item in source) {
40 destination[i] = item;
48 public bool debug_enabled;
50 public void print_debug(string text) {
57 public struct Fraction {
59 public int denominator;
61 public Fraction(int numerator, int denominator) {
62 this.numerator = numerator;
63 this.denominator = denominator;
66 public Fraction.from_string(string s) {
67 string[] elements = s.split("/");
68 if (elements.length != 2) {
72 numerator = elements[0].to_int();
73 denominator = elements[1].to_int();
77 public bool equal(Fraction f) {
78 if (float_abs(((numerator / (float)denominator) - (f.numerator / (float)f.denominator))) <=
84 public int nearest_int() {
85 return (int) (((double) numerator / denominator) + 0.5);
88 public string to_string() {
89 return "%d/%d".printf(numerator, denominator);
94 public struct TimeCode {
99 public bool drop_code;
101 public void get_from_length(int64 length) {
102 length /= Gst.SECOND;
104 hour = (int) (length / 3600);
105 minute = (int) ((length % 3600) / 60);
106 second = (int) ((length % 3600) % 60);
110 public string to_string() {
113 ret += "%.2d:".printf(hour);
115 ret += "%.2d:".printf(minute);
116 ret += "%.2d".printf(second);
122 ret += "%.2d".printf(frame);
128 public bool time_in_range(int64 time, int64 center, int64 delta) {
129 int64 diff = time - center;
130 return diff.abs() <= delta;
133 public string isolate_filename(string path) {
134 string str = Path.get_basename(path);
135 return str.split(".")[0];
138 public string get_file_extension(string path) {
139 unowned string dot = path.rchr(-1, '.');
140 return dot == null ? "" : dot.next_char();
143 public string append_extension(string path, string extension) {
144 if (get_file_extension(path) == extension)
147 return path + "." + extension;
150 // Given two version number strings such as "0.10.2.4", return true if the first is
151 // greater than or equal to the second.
152 public bool version_at_least(string v, string w) {
153 string[] va = v.split(".");
154 string[] wa = w.split(".");
155 for (int i = 0 ; i < wa.length ; ++i) {
158 int vi = va[i].to_int();
159 int wi = wa[i].to_int();
168 public bool get_file_md5_checksum(string filename, out string checksum) {
169 string new_filename = append_extension(filename, "md5");
171 size_t buffer_length;
173 GLib.FileUtils.get_contents(new_filename, out checksum, out buffer_length);
174 } catch (GLib.FileError e) {
178 return buffer_length == 32;
181 public void save_file_md5_checksum(string filename, string checksum) {
182 string new_filename = append_extension(filename, "md5");
185 GLib.FileUtils.set_contents(new_filename, checksum);
186 } catch (GLib.FileError e) {
187 error("Cannot save md5 file %s!\n", new_filename);
191 public bool md5_checksum_on_file(string filename, out string checksum) {
196 GLib.FileUtils.get_contents(filename, out file_buffer, out len);
197 } catch (GLib.FileError e) {
201 GLib.Checksum c = new GLib.Checksum(GLib.ChecksumType.MD5);
202 c.update((uchar[]) file_buffer, len);
203 checksum = c.get_string();
207 // GDK/GTK utility functions
209 // constants from gdkkeysyms.h https://bugzilla.gnome.org/show_bug.cgi?id=551184
210 [CCode (cprefix = "GDK_", has_type_id = "0", cheader_filename = "gdk/gdkkeysyms.h")]
211 public enum KeySyms {
231 public Gdk.ModifierType GDK_SHIFT_ALT_CONTROL_MASK = Gdk.ModifierType.SHIFT_MASK |
232 Gdk.ModifierType.MOD1_MASK |
233 Gdk.ModifierType.CONTROL_MASK;
235 public const Gtk.TargetEntry[] drag_target_entries = {
236 { "text/uri-list", 0, 0 }
239 public Gdk.Color parse_color(string color) {
241 if (!Gdk.Color.parse(color, out c))
242 error("can't parse color");
246 public Gtk.Widget get_widget(Gtk.UIManager manager, string name) {
247 Gtk.Widget widget = manager.get_widget(name);
249 error("can't find widget");
253 /////////////////////////////////////////////////////////////////////////////
254 // Rectangle drawing stuff //
255 // Original rounded rectangle code from: http://cairographics.org/samples/ //
256 /////////////////////////////////////////////////////////////////////////////
258 const double LINE_WIDTH = 1.0;
259 const double RADIUS = 15.0;
260 const Cairo.Antialias ANTIALIAS = Cairo.Antialias.DEFAULT; // NONE/DEFAULT
262 public void draw_rounded_rectangle(Gdk.Window window, Gdk.Color color, bool filled,
263 int x0, int y0, int width, int height) {
264 if (width == 0 || height == 0)
267 double x1 = x0 + width;
268 double y1 = y0 + height;
270 Cairo.Context cairo_window = Gdk.cairo_create(window);
271 Gdk.cairo_set_source_color(cairo_window, color);
272 cairo_window.set_antialias(ANTIALIAS);
274 if ((width / 2) < RADIUS) {
275 if ((height / 2) < RADIUS) {
276 cairo_window.move_to(x0, ((y0 + y1) / 2));
277 cairo_window.curve_to(x0, y0, x0, y0, (x0 + x1) / 2, y0);
278 cairo_window.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2);
279 cairo_window.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1);
280 cairo_window.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2);
282 cairo_window.move_to(x0, y0 + RADIUS);
283 cairo_window.curve_to(x0,y0, x0, y0, (x0 + x1) / 2, y0);
284 cairo_window.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS);
285 cairo_window.line_to(x1, y1 - RADIUS);
286 cairo_window.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1);
287 cairo_window.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS);
290 if ((height / 2) < RADIUS) {
291 cairo_window.move_to(x0, (y0 + y1) / 2);
292 cairo_window.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0);
293 cairo_window.line_to(x1 - RADIUS, y0);
294 cairo_window.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2);
295 cairo_window.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1);
296 cairo_window.line_to(x0 + RADIUS, y1);
297 cairo_window.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2);
299 cairo_window.move_to(x0, y0 + RADIUS);
300 cairo_window.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0);
301 cairo_window.line_to(x1 - RADIUS, y0);
302 cairo_window.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS);
303 cairo_window.line_to(x1, y1 - RADIUS);
304 cairo_window.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1);
305 cairo_window.line_to(x0 + RADIUS, y1);
306 cairo_window.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS);
309 cairo_window.close_path();
314 cairo_window.set_line_width(LINE_WIDTH);
315 cairo_window.stroke();
319 public void draw_right_rounded_rectangle(Gdk.Window window, Gdk.Color color, bool filled,
320 int x0, int y0, int width, int height) {
321 if (width == 0 || height == 0)
324 double x1 = x0 + width;
325 double y1 = y0 + height;
327 Cairo.Context cairo_window = Gdk.cairo_create(window);
328 Gdk.cairo_set_source_color(cairo_window, color);
329 cairo_window.set_antialias(ANTIALIAS);
331 if ((width / 2) < RADIUS) {
332 if ((height / 2) < RADIUS) {
333 cairo_window.move_to(x0, y0);
334 cairo_window.line_to((x0 + x1) / 2, y0);
335 cairo_window.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2);
336 cairo_window.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1);
337 cairo_window.line_to(x0, y1);
338 cairo_window.line_to(x0, y0);
340 cairo_window.move_to(x0, y0);
341 cairo_window.line_to((x0 + x1) / 2, y0);
342 cairo_window.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS);
343 cairo_window.line_to(x1, y1 - RADIUS);
344 cairo_window.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1);
345 cairo_window.line_to(x0, y1);
346 cairo_window.line_to(x0, y0);
349 if ((height / 2) < RADIUS) {
350 cairo_window.move_to(x0, y0);
351 cairo_window.line_to(x1 - RADIUS, y0);
352 cairo_window.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2);
353 cairo_window.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1);
354 cairo_window.line_to(x0, y1);
355 cairo_window.line_to(x0, y0);
357 cairo_window.move_to(x0, y0);
358 cairo_window.line_to(x1 - RADIUS, y0);
359 cairo_window.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS);
360 cairo_window.line_to(x1, y1 - RADIUS);
361 cairo_window.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1);
362 cairo_window.line_to(x0, y1);
363 cairo_window.line_to(x0, y0);
366 cairo_window.close_path();
371 cairo_window.set_line_width(LINE_WIDTH);
372 cairo_window.stroke();
376 public void draw_left_rounded_rectangle(Gdk.Window window, Gdk.Color color, bool filled,
377 int x0, int y0, int width, int height) {
378 if (width == 0 || height == 0)
381 double x1 = x0 + width;
382 double y1 = y0 + height;
384 Cairo.Context cairo_window = Gdk.cairo_create(window);
385 Gdk.cairo_set_source_color(cairo_window, color);
386 cairo_window.set_antialias(ANTIALIAS);
388 if ((width / 2) < RADIUS) {
389 if ((height / 2) < RADIUS) {
390 cairo_window.move_to(x0, ((y0 + y1) / 2));
391 cairo_window.curve_to(x0, y0, x0, y0, (x0 + x1) / 2, y0);
392 cairo_window.line_to(x1, y0);
393 cairo_window.line_to(x1, y1);
394 cairo_window.line_to((x1 + x0) / 2, y1);
395 cairo_window.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2);
397 cairo_window.move_to(x0, y0 + RADIUS);
398 cairo_window.curve_to(x0,y0, x0, y0, (x0 + x1) / 2, y0);
399 cairo_window.line_to(x1, y0);
400 cairo_window.line_to(x1, y1);
401 cairo_window.line_to((x1 + x0) / 2, y1);
402 cairo_window.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS);
405 if ((height / 2) < RADIUS) {
406 cairo_window.move_to(x0, (y0 + y1) / 2);
407 cairo_window.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0);
408 cairo_window.line_to(x1, y0);
409 cairo_window.line_to(x1, y1);
410 cairo_window.line_to(x0 + RADIUS, y1);
411 cairo_window.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2);
413 cairo_window.move_to(x0, y0 + RADIUS);
414 cairo_window.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0);
415 cairo_window.line_to(x1, y0);
416 cairo_window.line_to(x1, y1);
417 cairo_window.line_to(x0 + RADIUS, y1);
418 cairo_window.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS);
421 cairo_window.close_path();
426 cairo_window.set_line_width(LINE_WIDTH);
427 cairo_window.stroke();
431 public void draw_square_rectangle(Gdk.Window window, Gdk.Color color, bool filled,
432 int x, int y, int width, int height) {
433 if (width == 0 || height == 0)
436 Cairo.Context cairo_window = Gdk.cairo_create(window);
437 Gdk.cairo_set_source_color(cairo_window, color);
438 cairo_window.set_antialias(ANTIALIAS);
440 cairo_window.rectangle(x, y, width, height);
445 cairo_window.set_line_width(LINE_WIDTH);
446 cairo_window.stroke();
450 // GStreamer utility functions
452 public bool is_drop_frame_rate(Fraction r) {
453 return r.numerator == 2997 && r.denominator == 100 ||
454 r.numerator == 30000 && r.denominator == 1001;
457 public int64 frame_to_time_with_rate(int frame, Fraction rate) {
458 int64 time = (int64) Gst.util_uint64_scale(frame, Gst.SECOND * rate.denominator, rate.numerator);
462 public int time_to_frame_with_rate(int64 time, Fraction rate) {
463 int frame = (int) Gst.util_uint64_scale(time, rate.numerator, Gst.SECOND * rate.denominator);
465 /* We need frame_to_time_with_rate and time_to_frame_with_rate to be inverse functions, so that
466 * time_to_frame(frame_to_time_with_rate(f)) = f for all f. With the simple calculation
467 * above the functions might not be inverses due to rounding error, so we
468 * need the following check. */
469 return time >= frame_to_time_with_rate(frame + 1, rate) ? frame + 1 : frame;
472 public TimeCode frame_to_time(int frame, Fraction rate) {
478 if (rate.denominator == 1)
479 frame_rate = rate.numerator;
480 else if (is_drop_frame_rate(rate)) {
484 // We can't declare these as const int due to a Vala compiler bug.
485 int FRAMES_PER_MINUTE = 30 * 60 - 2;
486 int FRAMES_PER_10_MINUTES = 10 * FRAMES_PER_MINUTE + 2;
488 int block = frame / FRAMES_PER_10_MINUTES; // number of 10-minute blocks elapsed
489 int minute_in_block = (frame % FRAMES_PER_10_MINUTES - 2) / FRAMES_PER_MINUTE;
490 int minutes = 10 * block + minute_in_block;
491 frame += 2 * minutes - 2 * block; // skip 2 frames per minute, except every 10 minutes
493 // TODO: We're getting odd framerate fractions from imported videos, so
494 // I've removed the error call until we decide what to do
495 frame_rate = rate.numerator / rate.denominator;
498 t.frame = frame % frame_rate;
500 int64 secs = frame / frame_rate;
501 t.hour = (int) secs / 3600;
502 t.minute = ((int) secs % 3600) / 60;
503 t.second = ((int) secs % 3600) % 60;
508 public string frame_to_string(int frame, Fraction rate) {
509 return frame_to_time(frame, rate).to_string();
512 void breakup_time(int64 time, out int hours, out int minutes, out double seconds) {
513 int64 the_time = time;
514 int64 minute = Gst.SECOND * 60;
515 int64 hour = minute * 60;
516 hours = (int) (the_time / hour);
517 the_time = the_time % hour;
518 minutes = (int) (the_time / minute);
519 the_time = the_time % minute;
520 seconds = (double) the_time / Gst.SECOND;
523 public string time_to_HHMMSS(int64 time) {
528 breakup_time(time, out hours, out minutes, out seconds);
529 return "%02d:%02d:%05.2lf".printf(hours, minutes, seconds);
532 public string time_to_string(int64 time) {
537 breakup_time(time, out hours, out minutes, out seconds);
538 string return_value = "%1.2lfs".printf(seconds);
539 if (hours > 0 || minutes > 0) {
540 return_value = "%dm ".printf(minutes) + return_value;
544 return_value = "%dh ".printf(hours) + return_value;
550 public static Gst.Element make_element_with_name(string element_name, string? display_name)
552 Gst.Element e = Gst.ElementFactory.make(element_name, display_name);
555 MediaError.MISSING_PLUGIN("Could not create element %s(%s)".printf(element_name, display_name));
560 public static Gst.Element make_element(string name) throws Error {
561 return make_element_with_name(name, null);