2 * Dictix / DixRecorder - dix-record.c
4 * Copyright (C) Martin Blanchard 2011 <tinram@gmx.fr>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
29 #include <glib/gi18n.h>
33 #include "dix-recorder.h"
37 PROP_LAST_RECORD_TIMEVAL
,
48 static guint signals
[LAST_SIGNAL
] = { 0 };
50 struct _DixRecorderPrivate
58 GstElement
*converter
;
62 GDateTime
*last_record_timeval
;
66 static const gchar
*id_characters
= "0123456789abcdef";
68 static gchar
*days
[] = {
79 static gchar
*months
[] = {
95 #define DIX_RECORDER_ID_SIZE 32
96 #define DIX_RECORDER_ID_CHARACTERS_SIZE 16
98 #define DIX_RECORDER_GET_PRIVATE(obj) \
99 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), DIX_TYPE_RECORDER, DixRecorderPrivate))
101 G_DEFINE_TYPE (DixRecorder
, dix_recorder
, GST_TYPE_PIPELINE
)
103 static void dix_recorder_set_file (DixRecorder
*recorder
);
104 static void dix_recorder_write_title_tag (DixRecorder
*recorder
, GFile
*file
);
106 static void dix_recorder_error_cb (GstBus
*bus
, GstMessage
*message
, gpointer data
);
107 static void dix_recorder_element_cb (GstBus
*bus
, GstMessage
*message
, gpointer data
);
108 static void dix_recorder_state_changed_cb (GstBus
*bus
, GstMessage
*message
, gpointer data
);
109 static void dix_recorder_eos_cb (GstBus
*bus
, GstMessage
*message
, gpointer data
);
112 dix_recorder_set_property (GObject
*object
, guint prop_id
, const GValue
*value
, GParamSpec
*pspec
)
114 DixRecorder
*recorder
= (DixRecorder
*) object
;
115 DixRecorderPrivate
*priv
= recorder
->priv
;
119 case PROP_LAST_RECORD_TIMEVAL
: {
120 timeval
= (gint64
) g_value_get_uint64 (value
);
121 priv
->last_record_timeval
= g_date_time_new_from_unix_local (timeval
);
124 } case PROP_RECORDS_PATH
: {
125 priv
->path
= g_strdup (g_value_get_string (value
));
129 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
135 dix_recorder_get_property (GObject
*object
, guint prop_id
, GValue
*value
, GParamSpec
*pspec
)
137 DixRecorder
*recorder
= (DixRecorder
*) object
;
138 DixRecorderPrivate
*priv
= recorder
->priv
;
141 case PROP_RECORDS_PATH
: {
142 g_value_set_string (value
, g_strdup (priv
->path
));
146 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
152 dix_recorder_dispose (GObject
*object
)
154 DixRecorder
*recorder
= (DixRecorder
*) object
;
155 DixRecorderPrivate
*priv
= recorder
->priv
;
157 if (priv
->bus
!= NULL
) {
158 gst_object_unref (GST_OBJECT (priv
->bus
));
162 if (priv
->last_record_timeval
!= NULL
) {
163 g_date_time_unref (priv
->last_record_timeval
);
165 priv
->last_record_timeval
= NULL
;
167 G_OBJECT_CLASS (dix_recorder_parent_class
)->dispose (object
);
171 dix_recorder_finalize (GObject
*object
)
173 DixRecorder
*recorder
= (DixRecorder
*) object
;
174 DixRecorderPrivate
*priv
= recorder
->priv
;
177 if (priv
->id
!= NULL
) {
183 G_OBJECT_CLASS (dix_recorder_parent_class
)->finalize (object
);
187 dix_recorder_class_init (DixRecorderClass
* klass
)
189 GObjectClass
*gobject_class
= (GObjectClass
*) klass
;
191 gobject_class
->set_property
= dix_recorder_set_property
;
192 gobject_class
->get_property
= dix_recorder_get_property
;
193 gobject_class
->dispose
= dix_recorder_dispose
;
194 gobject_class
->finalize
= dix_recorder_finalize
;
196 g_object_class_install_property (gobject_class
,
197 PROP_LAST_RECORD_TIMEVAL
,
198 g_param_spec_uint64 ("last-record-timeval",
199 "Last record timeval",
200 "The timeval of last record (should be use only once at creation time).",
204 g_object_class_install_property (gobject_class
,
206 g_param_spec_string ("record-path",
208 "A path to the directory where records are put",
210 G_PARAM_CONSTRUCT_ONLY
| G_PARAM_READWRITE
));
212 signals
[RECORDING_STARTED
] = g_signal_new ("record-started",
213 G_TYPE_FROM_CLASS (gobject_class
),
215 G_STRUCT_OFFSET (DixRecorderClass
, record_started
),
217 g_cclosure_marshal_VOID__VOID
,
220 signals
[RECORDING_STOPPED
] = g_signal_new ("record-stopped",
221 G_TYPE_FROM_CLASS (gobject_class
),
223 G_STRUCT_OFFSET (DixRecorderClass
, record_stopped
),
225 g_cclosure_marshal_VOID__STRING
,
226 G_TYPE_NONE
, 1, G_TYPE_STRING
);
228 /*signals[PIPELINE_ERROR] = g_signal_new ("pipeline-error",
229 G_TYPE_FROM_CLASS (gobject_class),
231 G_STRUCT_OFFSET (DixRecorderClass, record_stopped),
233 g_cclosure_marshal_VOID__POINTER,
234 1, DIX_ERROR, G_TYPE_NONE, 0);*/
236 g_type_class_add_private (gobject_class
, sizeof (DixRecorderPrivate
));
240 dix_recorder_init (DixRecorder
* recorder
)
242 DixRecorderPrivate
*priv
= DIX_RECORDER_GET_PRIVATE (recorder
);
243 recorder
->priv
= priv
;
245 gst_object_set_name (GST_OBJECT(recorder
), "DictixRecorder");
253 priv
->converter
= NULL
;
254 priv
->encoder
= NULL
;
257 priv
->last_record_timeval
= NULL
;
258 priv
->record_count
= 1;
262 dix_recorder_new (const gchar
* directory
)
264 g_return_val_if_fail ((directory
!= NULL
), NULL
);
266 return g_object_new (DIX_TYPE_RECORDER
, "record-path", directory
, NULL
);
270 dix_recorder_start_recording (DixRecorder
*recorder
, GError
**error
)
272 g_return_if_fail (DIX_IS_RECORDER (recorder
));
273 DixRecorderPrivate
*priv
= recorder
->priv
;
275 if (priv
->id
!= NULL
) {
276 /* We are alredy recording ... */
280 if (G_UNLIKELY (priv
->bus
== NULL
)) {
281 priv
->source
= gst_element_factory_make ("pulsesrc", "DictixRecorderSource");
282 priv
->converter
= gst_element_factory_make ("audioconvert", "DictixRecorderConverter");
283 priv
->encoder
= gst_element_factory_make ("flacenc", "DictixRecorderEncoder");
284 priv
->sink
= gst_element_factory_make ("giosink", "DictixRecorderSink");
286 if (priv
->source
== NULL
|| priv
->converter
== NULL
287 || priv
->encoder
== NULL
|| priv
->sink
== NULL
) {
288 /* *error = g_error_new (DIX_ERROR, DIX_ERROR_GST_MISSING_PLUGINS,*/
289 /* _("You need the \"good\" set of gstreamer's plugin."));*/
294 gst_bin_add_many (GST_BIN (recorder
),
301 gst_element_link_many (priv
->source
,
307 priv
->bus
= gst_pipeline_get_bus (GST_PIPELINE (recorder
));
308 gst_bus_add_signal_watch (priv
->bus
);
309 g_signal_connect (priv
->bus
, "message::error",
310 G_CALLBACK (dix_recorder_error_cb
), recorder
);
311 g_signal_connect (priv
->bus
, "message::element",
312 G_CALLBACK (dix_recorder_element_cb
), recorder
);
313 g_signal_connect (priv
->bus
, "message::state-changed",
314 G_CALLBACK (dix_recorder_state_changed_cb
), recorder
);
315 g_signal_connect (priv
->bus
, "message::eos",
316 G_CALLBACK (dix_recorder_eos_cb
), recorder
);
319 dix_recorder_set_file (recorder
);
321 gst_element_set_state (GST_ELEMENT (recorder
), GST_STATE_PLAYING
);
325 dix_recorder_stop_recording (DixRecorder
*recorder
)
327 g_return_if_fail (DIX_IS_RECORDER (recorder
));
328 DixRecorderPrivate
*priv
= recorder
->priv
;
330 if (G_UNLIKELY (priv
->id
== NULL
)) {
331 /* We are not recording... */
335 gst_element_set_state (priv
->source
, GST_STATE_NULL
); /* Will emit EOS */
336 gst_element_get_state (priv
->source
, NULL
, NULL
, GST_CLOCK_TIME_NONE
);
337 gst_element_set_locked_state (priv
->source
, TRUE
);
341 dix_recorder_set_file (DixRecorder
*recorder
)
343 DixRecorderPrivate
*priv
= recorder
->priv
;
348 if (priv
->id
!= NULL
) {
352 id
= g_malloc (sizeof (gchar
) * (DIX_RECORDER_ID_SIZE
+ 1));
353 id
[DIX_RECORDER_ID_SIZE
] = 0;
355 while (i
< DIX_RECORDER_ID_SIZE
) {
359 random
= g_random_double ();
360 character
= random
* (gdouble
) (DIX_RECORDER_ID_CHARACTERS_SIZE
- 1);
361 id
[i
] = id_characters
[character
];
369 file
= g_build_filename (priv
->path
,
375 priv
->id
= g_strdup (id
);
376 uri
= g_filename_to_uri (file
, NULL
, NULL
);
377 g_object_set (G_OBJECT (priv
->sink
), "location", uri
, NULL
);
385 dix_recorder_write_title_tag (DixRecorder
*recorder
, GFile
*file
)
387 DixRecorderPrivate
*priv
= recorder
->priv
;
388 GError
*error
= NULL
;
390 GDateTime
*time
= NULL
;
391 GTimeSpan difference
= 0;
393 gchar
*suffix
= NULL
;
395 gint weekday
, day
, month
, hour
, minute
;
398 title
= _("Unknow record");
400 info
= g_file_query_info (file
,
401 G_FILE_ATTRIBUTE_TIME_MODIFIED
,
402 G_FILE_QUERY_INFO_NONE
,
407 g_warning (error
->message
);
408 title
= _("Unknow record");
410 date
= (gint64
) g_file_info_get_attribute_uint64 (info
, G_FILE_ATTRIBUTE_TIME_MODIFIED
);
411 time
= g_date_time_new_from_unix_local (date
);
413 g_object_unref (G_OBJECT (info
));
415 weekday
= g_date_time_get_day_of_week (time
);
416 day
= g_date_time_get_day_of_month (time
);
417 month
= g_date_time_get_month (time
);
418 hour
= g_date_time_get_hour (time
);
419 minute
= g_date_time_get_minute (time
);
421 if (priv
->last_record_timeval
!= NULL
) {
422 difference
= g_date_time_difference (time
, priv
->last_record_timeval
);
423 difference
/= G_TIME_SPAN_SECOND
;
425 if (difference
< 60) {
426 if (difference
< (60 - g_date_time_get_seconds (priv
->last_record_timeval
))) {
427 priv
->record_count
++;
428 suffix
= g_strconcat (" [",
429 g_strdup_printf ("%u", priv
->record_count
),
433 priv
->record_count
= 1;
437 g_date_time_unref (priv
->last_record_timeval
);
439 priv
->last_record_timeval
= time
;
441 title
= g_strconcat (days
[weekday
],
442 g_strdup_printf (" %d ", day
),
444 g_strdup_printf (" %02d", hour
),
445 g_strdup_printf (":%02d", minute
),
446 suffix
, /* May be NULL */
451 if (g_utf8_validate (title
, -1, NULL
)) {
452 gst_tag_setter_add_tags (GST_TAG_SETTER (priv
->encoder
),
453 GST_TAG_MERGE_REPLACE
,
454 GST_TAG_TITLE
, title
, /* Duplicate title ? */
458 if (suffix
!= NULL
) {
465 dix_recorder_error_cb (GstBus
*bus
, GstMessage
*message
, gpointer data
)
467 DixRecorder
*recorder
= (DixRecorder
*) data
;
468 DixRecorderPrivate
*priv
= recorder
->priv
;
471 /* error = g_error_new (DIX_ERROR, DIX_ERROR_GST_INTERNAL,*/
472 /* _("Audio engine internal error"));*/
476 dix_recorder_element_cb (GstBus
*bus
, GstMessage
*message
, gpointer data
)
478 DixRecorder
*recorder
= (DixRecorder
*) data
;
479 DixRecorderPrivate
*priv
= recorder
->priv
;
480 const GstStructure
*structure
;
482 if (message
->src
!= GST_OBJECT (priv
->sink
)) {
486 structure
= gst_message_get_structure (message
);
487 if (gst_structure_has_name (structure
, "file-exists")) {
488 /* File alredy exists, shoud not happened... */
489 gst_bus_set_flushing (priv
->bus
, TRUE
);
492 dix_recorder_set_file (recorder
);
494 gst_bus_set_flushing (priv
->bus
, FALSE
);
495 gst_element_set_state (GST_ELEMENT (recorder
), GST_STATE_PLAYING
);
500 dix_recorder_state_changed_cb (GstBus
*bus
, GstMessage
*message
, gpointer data
)
502 DixRecorder
*recorder
= (DixRecorder
*) data
;
503 DixRecorderPrivate
*priv
= recorder
->priv
;
507 gst_message_parse_state_changed (message
, NULL
, &state
, NULL
);
508 if (message
->src
!= GST_OBJECT (recorder
)) {
513 case GST_STATE_NULL
: {
515 } case GST_STATE_READY
: {
516 g_object_get (G_OBJECT (priv
->sink
), "file", &file
, NULL
);
517 dix_recorder_write_title_tag (recorder
, file
);
519 } case GST_STATE_PLAYING
: {
520 g_signal_emit (recorder
, signals
[RECORDING_STARTED
], 0);
529 dix_recorder_eos_cb (GstBus
*bus
, GstMessage
*message
, gpointer data
)
531 DixRecorder
*recorder
= (DixRecorder
*) data
;
532 DixRecorderPrivate
*priv
= recorder
->priv
;
535 gst_element_set_state (GST_ELEMENT (recorder
), GST_STATE_NULL
);
536 gst_element_get_state (GST_ELEMENT (recorder
), NULL
, NULL
, GST_CLOCK_TIME_NONE
);
538 g_object_get (G_OBJECT (priv
->sink
), "location", &uri
, NULL
);
540 g_signal_emit (recorder
, signals
[RECORDING_STOPPED
], 0, uri
);
545 gst_element_set_locked_state (priv
->source
, FALSE
);