X-Git-Url: http://git.maemo.org/git/?p=fillmore;a=blobdiff_plain;f=src%2Fmarina%2Futil.vala;fp=src%2Fmarina%2Futil.vala;h=576324a44d46f5bcb8e0d4e1cbf77e8e36b4d6c6;hp=0000000000000000000000000000000000000000;hb=a712cd772f4f3db8bed7037bb95c4de94767b230;hpb=2f0296582bf5d3f51db40d299f434fc8240ca6a5 diff --git a/src/marina/util.vala b/src/marina/util.vala new file mode 100644 index 0000000..576324a --- /dev/null +++ b/src/marina/util.vala @@ -0,0 +1,563 @@ +/* Copyright 2009 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +public errordomain MediaError { + MISSING_PLUGIN +} + +// I can't find a floating point absolute value function in Vala... +public float float_abs(float f) { + if (f < 0.0f) + return -f; + return f; +} + +public bool float_within(double f, double epsilon) { + return float_abs((float) f) < epsilon; +} + +public int sign(int x) { + if (x == 0) + return 0; + return x < 0 ? -1 : 1; +} + +int stricmp(string str1, string str2) { + string temp_str1 = str1.casefold(-1); + string temp_str2 = str2.casefold(-1); + + return temp_str1.collate(temp_str2); +} + +// TODO: write this using generics. +public string[] copy_array(string[] source) { + string[] destination = new string[source.length]; + int i = 0; + foreach (string item in source) { + destination[i] = item; + ++i; + } + return destination; +} + +// Debug utilities + +public bool debug_enabled; + +public void print_debug(string text) { + if (!debug_enabled) + return; + + debug("%s", text); +} + +public struct Fraction { + public int numerator; + public int denominator; + + public Fraction(int numerator, int denominator) { + this.numerator = numerator; + this.denominator = denominator; + } + + public Fraction.from_string(string s) { + string[] elements = s.split("/"); + if (elements.length != 2) { + numerator = 0; + denominator = 0; + } else { + numerator = elements[0].to_int(); + denominator = elements[1].to_int(); + } + } + + public bool equal(Fraction f) { + if (float_abs(((numerator / (float)denominator) - (f.numerator / (float)f.denominator))) <= + (1000.0f / 1001.0f)) + return true; + return false; + } + + public int nearest_int() { + return (int) (((double) numerator / denominator) + 0.5); + } + + public string to_string() { + return "%d/%d".printf(numerator, denominator); + } + +} + +public struct TimeCode { + public int hour; + public int minute; + public int second; + public int frame; + public bool drop_code; + + public void get_from_length(int64 length) { + length /= Gst.SECOND; + + hour = (int) (length / 3600); + minute = (int) ((length % 3600) / 60); + second = (int) ((length % 3600) % 60); + frame = 0; + } + + public string to_string() { + string ret = ""; + if (hour != 0) + ret += "%.2d:".printf(hour); + + ret += "%.2d:".printf(minute); + ret += "%.2d".printf(second); + + if (drop_code) + ret += ";"; + else + ret += ":"; + ret += "%.2d".printf(frame); + + return ret; + } +} + +public bool time_in_range(int64 time, int64 center, int64 delta) { + int64 diff = time - center; + return diff.abs() <= delta; +} + +public string isolate_filename(string path) { + string str = Path.get_basename(path); + return str.split(".")[0]; +} + +public string get_file_extension(string path) { + unowned string dot = path.rchr(-1, '.'); + return dot == null ? "" : dot.next_char(); +} + +public string append_extension(string path, string extension) { + if (get_file_extension(path) == extension) + return path; + + return path + "." + extension; +} + +// Given two version number strings such as "0.10.2.4", return true if the first is +// greater than or equal to the second. +public bool version_at_least(string v, string w) { + string[] va = v.split("."); + string[] wa = w.split("."); + for (int i = 0 ; i < wa.length ; ++i) { + if (i >= va.length) + return false; + int vi = va[i].to_int(); + int wi = wa[i].to_int(); + if (vi > wi) + return true; + if (wi > vi) + return false; + } + return true; +} + +public bool get_file_md5_checksum(string filename, out string checksum) { + string new_filename = append_extension(filename, "md5"); + + size_t buffer_length; + try { + GLib.FileUtils.get_contents(new_filename, out checksum, out buffer_length); + } catch (GLib.FileError e) { + return false; + } + + return buffer_length == 32; +} + +public void save_file_md5_checksum(string filename, string checksum) { + string new_filename = append_extension(filename, "md5"); + + try { + GLib.FileUtils.set_contents(new_filename, checksum); + } catch (GLib.FileError e) { + error("Cannot save md5 file %s!\n", new_filename); + } +} + +public bool md5_checksum_on_file(string filename, out string checksum) { + string file_buffer; + size_t len; + + try { + GLib.FileUtils.get_contents(filename, out file_buffer, out len); + } catch (GLib.FileError e) { + return false; + } + + GLib.Checksum c = new GLib.Checksum(GLib.ChecksumType.MD5); + c.update((uchar[]) file_buffer, len); + checksum = c.get_string(); + return true; +} + +// GDK/GTK utility functions + +// constants from gdkkeysyms.h https://bugzilla.gnome.org/show_bug.cgi?id=551184 +[CCode (cprefix = "GDK_", has_type_id = "0", cheader_filename = "gdk/gdkkeysyms.h")] +public enum KeySyms { + Control_L, + Control_R, + Down, + equal, + Escape, + KP_Add, + KP_Enter, + KP_Subtract, + Left, + minus, + plus, + Return, + Right, + Shift_L, + Shift_R, + underscore, + Up +} + +public Gdk.ModifierType GDK_SHIFT_ALT_CONTROL_MASK = Gdk.ModifierType.SHIFT_MASK | + Gdk.ModifierType.MOD1_MASK | + Gdk.ModifierType.CONTROL_MASK; + +public const Gtk.TargetEntry[] drag_target_entries = { + { "text/uri-list", 0, 0 } +}; + +public Gdk.Color parse_color(string color) { + Gdk.Color c; + if (!Gdk.Color.parse(color, out c)) + error("can't parse color"); + return c; +} + +public Gtk.Widget get_widget(Gtk.UIManager manager, string name) { + Gtk.Widget widget = manager.get_widget(name); + if (widget == null) + error("can't find widget"); + return widget; +} + +///////////////////////////////////////////////////////////////////////////// +// Rectangle drawing stuff // +// Original rounded rectangle code from: http://cairographics.org/samples/ // +///////////////////////////////////////////////////////////////////////////// + +const double LINE_WIDTH = 1.0; +const double RADIUS = 15.0; +const Cairo.Antialias ANTIALIAS = Cairo.Antialias.DEFAULT; // NONE/DEFAULT + +public void draw_rounded_rectangle(Gdk.Window window, Gdk.Color color, bool filled, + int x0, int y0, int width, int height) { + if (width == 0 || height == 0) + return; + + double x1 = x0 + width; + double y1 = y0 + height; + + Cairo.Context cairo_window = Gdk.cairo_create(window); + Gdk.cairo_set_source_color(cairo_window, color); + cairo_window.set_antialias(ANTIALIAS); + + if ((width / 2) < RADIUS) { + if ((height / 2) < RADIUS) { + cairo_window.move_to(x0, ((y0 + y1) / 2)); + cairo_window.curve_to(x0, y0, x0, y0, (x0 + x1) / 2, y0); + cairo_window.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2); + cairo_window.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1); + cairo_window.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2); + } else { + cairo_window.move_to(x0, y0 + RADIUS); + cairo_window.curve_to(x0,y0, x0, y0, (x0 + x1) / 2, y0); + cairo_window.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS); + cairo_window.line_to(x1, y1 - RADIUS); + cairo_window.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1); + cairo_window.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS); + } + } else { + if ((height / 2) < RADIUS) { + cairo_window.move_to(x0, (y0 + y1) / 2); + cairo_window.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0); + cairo_window.line_to(x1 - RADIUS, y0); + cairo_window.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2); + cairo_window.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1); + cairo_window.line_to(x0 + RADIUS, y1); + cairo_window.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2); + } else { + cairo_window.move_to(x0, y0 + RADIUS); + cairo_window.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0); + cairo_window.line_to(x1 - RADIUS, y0); + cairo_window.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS); + cairo_window.line_to(x1, y1 - RADIUS); + cairo_window.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1); + cairo_window.line_to(x0 + RADIUS, y1); + cairo_window.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS); + } + } + cairo_window.close_path(); + + if (filled) { + cairo_window.fill(); + } else { + cairo_window.set_line_width(LINE_WIDTH); + cairo_window.stroke(); + } +} + +public void draw_right_rounded_rectangle(Gdk.Window window, Gdk.Color color, bool filled, + int x0, int y0, int width, int height) { + if (width == 0 || height == 0) + return; + + double x1 = x0 + width; + double y1 = y0 + height; + + Cairo.Context cairo_window = Gdk.cairo_create(window); + Gdk.cairo_set_source_color(cairo_window, color); + cairo_window.set_antialias(ANTIALIAS); + + if ((width / 2) < RADIUS) { + if ((height / 2) < RADIUS) { + cairo_window.move_to(x0, y0); + cairo_window.line_to((x0 + x1) / 2, y0); + cairo_window.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2); + cairo_window.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1); + cairo_window.line_to(x0, y1); + cairo_window.line_to(x0, y0); + } else { + cairo_window.move_to(x0, y0); + cairo_window.line_to((x0 + x1) / 2, y0); + cairo_window.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS); + cairo_window.line_to(x1, y1 - RADIUS); + cairo_window.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1); + cairo_window.line_to(x0, y1); + cairo_window.line_to(x0, y0); + } + } else { + if ((height / 2) < RADIUS) { + cairo_window.move_to(x0, y0); + cairo_window.line_to(x1 - RADIUS, y0); + cairo_window.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2); + cairo_window.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1); + cairo_window.line_to(x0, y1); + cairo_window.line_to(x0, y0); + } else { + cairo_window.move_to(x0, y0); + cairo_window.line_to(x1 - RADIUS, y0); + cairo_window.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS); + cairo_window.line_to(x1, y1 - RADIUS); + cairo_window.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1); + cairo_window.line_to(x0, y1); + cairo_window.line_to(x0, y0); + } + } + cairo_window.close_path(); + + if (filled) { + cairo_window.fill(); + } else { + cairo_window.set_line_width(LINE_WIDTH); + cairo_window.stroke(); + } +} + +public void draw_left_rounded_rectangle(Gdk.Window window, Gdk.Color color, bool filled, + int x0, int y0, int width, int height) { + if (width == 0 || height == 0) + return; + + double x1 = x0 + width; + double y1 = y0 + height; + + Cairo.Context cairo_window = Gdk.cairo_create(window); + Gdk.cairo_set_source_color(cairo_window, color); + cairo_window.set_antialias(ANTIALIAS); + + if ((width / 2) < RADIUS) { + if ((height / 2) < RADIUS) { + cairo_window.move_to(x0, ((y0 + y1) / 2)); + cairo_window.curve_to(x0, y0, x0, y0, (x0 + x1) / 2, y0); + cairo_window.line_to(x1, y0); + cairo_window.line_to(x1, y1); + cairo_window.line_to((x1 + x0) / 2, y1); + cairo_window.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2); + } else { + cairo_window.move_to(x0, y0 + RADIUS); + cairo_window.curve_to(x0,y0, x0, y0, (x0 + x1) / 2, y0); + cairo_window.line_to(x1, y0); + cairo_window.line_to(x1, y1); + cairo_window.line_to((x1 + x0) / 2, y1); + cairo_window.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS); + } + } else { + if ((height / 2) < RADIUS) { + cairo_window.move_to(x0, (y0 + y1) / 2); + cairo_window.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0); + cairo_window.line_to(x1, y0); + cairo_window.line_to(x1, y1); + cairo_window.line_to(x0 + RADIUS, y1); + cairo_window.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2); + } else { + cairo_window.move_to(x0, y0 + RADIUS); + cairo_window.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0); + cairo_window.line_to(x1, y0); + cairo_window.line_to(x1, y1); + cairo_window.line_to(x0 + RADIUS, y1); + cairo_window.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS); + } + } + cairo_window.close_path(); + + if (filled) { + cairo_window.fill(); + } else { + cairo_window.set_line_width(LINE_WIDTH); + cairo_window.stroke(); + } +} + +public void draw_square_rectangle(Gdk.Window window, Gdk.Color color, bool filled, + int x, int y, int width, int height) { + if (width == 0 || height == 0) + return; + + Cairo.Context cairo_window = Gdk.cairo_create(window); + Gdk.cairo_set_source_color(cairo_window, color); + cairo_window.set_antialias(ANTIALIAS); + + cairo_window.rectangle(x, y, width, height); + + if (filled) { + cairo_window.fill(); + } else { + cairo_window.set_line_width(LINE_WIDTH); + cairo_window.stroke(); + } +} + +// GStreamer utility functions + +public bool is_drop_frame_rate(Fraction r) { + return r.numerator == 2997 && r.denominator == 100 || + r.numerator == 30000 && r.denominator == 1001; +} + +public int64 frame_to_time_with_rate(int frame, Fraction rate) { + int64 time = (int64) Gst.util_uint64_scale(frame, Gst.SECOND * rate.denominator, rate.numerator); + return time; +} + +public int time_to_frame_with_rate(int64 time, Fraction rate) { + int frame = (int) Gst.util_uint64_scale(time, rate.numerator, Gst.SECOND * rate.denominator); + + /* We need frame_to_time_with_rate and time_to_frame_with_rate to be inverse functions, so that + * time_to_frame(frame_to_time_with_rate(f)) = f for all f. With the simple calculation + * above the functions might not be inverses due to rounding error, so we + * need the following check. */ + return time >= frame_to_time_with_rate(frame + 1, rate) ? frame + 1 : frame; +} + +public TimeCode frame_to_time(int frame, Fraction rate) { + int frame_rate = 0; + + TimeCode t = {}; + + t.drop_code = false; + if (rate.denominator == 1) + frame_rate = rate.numerator; + else if (is_drop_frame_rate(rate)) { + t.drop_code = true; + frame_rate = 30; + + // We can't declare these as const int due to a Vala compiler bug. + int FRAMES_PER_MINUTE = 30 * 60 - 2; + int FRAMES_PER_10_MINUTES = 10 * FRAMES_PER_MINUTE + 2; + + int block = frame / FRAMES_PER_10_MINUTES; // number of 10-minute blocks elapsed + int minute_in_block = (frame % FRAMES_PER_10_MINUTES - 2) / FRAMES_PER_MINUTE; + int minutes = 10 * block + minute_in_block; + frame += 2 * minutes - 2 * block; // skip 2 frames per minute, except every 10 minutes + } else { + // TODO: We're getting odd framerate fractions from imported videos, so + // I've removed the error call until we decide what to do + frame_rate = rate.numerator / rate.denominator; + } + + t.frame = frame % frame_rate; + + int64 secs = frame / frame_rate; + t.hour = (int) secs / 3600; + t.minute = ((int) secs % 3600) / 60; + t.second = ((int) secs % 3600) % 60; + + return t; +} + +public string frame_to_string(int frame, Fraction rate) { + return frame_to_time(frame, rate).to_string(); +} + +void breakup_time(int64 time, out int hours, out int minutes, out double seconds) { + int64 the_time = time; + int64 minute = Gst.SECOND * 60; + int64 hour = minute * 60; + hours = (int) (the_time / hour); + the_time = the_time % hour; + minutes = (int) (the_time / minute); + the_time = the_time % minute; + seconds = (double) the_time / Gst.SECOND; +} + +public string time_to_HHMMSS(int64 time) { + int hours; + int minutes; + double seconds; + + breakup_time(time, out hours, out minutes, out seconds); + return "%02d:%02d:%05.2lf".printf(hours, minutes, seconds); +} + +public string time_to_string(int64 time) { + int hours; + int minutes; + double seconds; + + breakup_time(time, out hours, out minutes, out seconds); + string return_value = "%1.2lfs".printf(seconds); + if (hours > 0 || minutes > 0) { + return_value = "%dm ".printf(minutes) + return_value; + } + + if (hours > 0) { + return_value = "%dh ".printf(hours) + return_value; + } + + return return_value; +} + +public static Gst.Element make_element_with_name(string element_name, string? display_name) + throws GLib.Error { + Gst.Element e = Gst.ElementFactory.make(element_name, display_name); + if (e == null) { + throw new + MediaError.MISSING_PLUGIN("Could not create element %s(%s)".printf(element_name, display_name)); + } + return e; +} + +public static Gst.Element make_element(string name) throws Error { + return make_element_with_name(name, null); +} +