[Debconf-video-commits] r415 - in package/branches/pycon09: conf src src/debconfvideo src/pyconvideo/pyconvideo

benh at alioth.debian.org benh at alioth.debian.org
Sun Mar 29 18:12:28 UTC 2009


Author: benh
Date: 2009-03-29 18:12:28 +0000 (Sun, 29 Mar 2009)
New Revision: 415

Modified:
   package/branches/pycon09/conf/permissions.sql
   package/branches/pycon09/src/dc-do-transcoding
   package/branches/pycon09/src/dc-video-schema.sql
   package/branches/pycon09/src/debconfvideo/__init__.py
   package/branches/pycon09/src/pyconvideo/pyconvideo/admin.py
   package/branches/pycon09/src/pyconvideo/pyconvideo/models.py
   package/branches/pycon09/src/pyconvideo/pyconvideo/review_event_recording.html
   package/branches/pycon09/src/pyconvideo/pyconvideo/videorecording_list.html
   package/branches/pycon09/src/pyconvideo/pyconvideo/views.py
Log:
Concatenate multiple recordings of the same event
- Use a single target base name per event
- Lock events being reviewed
- Do not transcode recordings for locked events, since they may get more
  recordings later
- Concatenate multiple recordings of an event in order of recording start time


Modified: package/branches/pycon09/conf/permissions.sql
===================================================================
--- package/branches/pycon09/conf/permissions.sql	2009-03-28 22:20:31 UTC (rev 414)
+++ package/branches/pycon09/conf/permissions.sql	2009-03-29 18:12:28 UTC (rev 415)
@@ -1,7 +1,7 @@
 -- Permissions required by scripts running as group videoteam.
 
 GRANT SELECT
-ON conference, conference_room, event, video_event_recording,
+ON conference, conference_room, event, video_event, video_event_recording,
    video_file_status, video_target_format, video_encoding_host
 TO videoteam;
 

Modified: package/branches/pycon09/src/dc-do-transcoding
===================================================================
--- package/branches/pycon09/src/dc-do-transcoding	2009-03-28 22:20:31 UTC (rev 414)
+++ package/branches/pycon09/src/dc-do-transcoding	2009-03-29 18:12:28 UTC (rev 415)
@@ -23,59 +23,78 @@
 class Job(object):
     pass
 
+class Recording(object):
+    pass
+
 def get_job_queue():
     attrs = [
-        'video_event_recording.id', 'video_target_format.id',
-        'recording_filename', 'start_time', 'end_time',
+        'video_event.event_id', 'video_target_format.id',
         'container_name',
         'video_width', 'video_height', 'video_codec_name', 'video_bit_rate',
         'audio_sample_rate', 'audio_channel_count', 'audio_codec_name',
         'audio_bit_rate',
-        'target_format_abbr', 'event_recording_base_name',
-        'filename_extension',
-        'priority', 'recording_time'
+        'target_format_abbr', 'event_base_name', 'filename_extension',
+        'priority', 'start_time'
         ]
-    cur = database.get_cursor()
-    cur.execute('SELECT ' + ', '.join(attrs) + """ FROM
-((video_recording JOIN video_event_recording
-  ON video_recording.id = video_event_recording.recording_id)
- JOIN video_file_status
- ON video_recording.file_status_id = video_file_status.id),
+    jobs = []
+
+    job_cur = database.get_cursor()
+    rec_cur = database.get_cursor()
+
+    job_cur.execute('SELECT ' + ', '.join(attrs) + """ FROM
+(event JOIN video_event ON event.event_id = video_event.event_id),
 video_target_format
 -- Filter out existing target files.
--- Only work on recordings that have has been rated as valid.
 WHERE NOT EXISTS (SELECT * from video_target_file
-                  WHERE video_target_file.event_recording_id = video_event_recording.id
+                  WHERE video_target_file.event_id = video_event.event_id
                   AND video_target_file.target_format_id = video_target_format.id)
-      AND video_file_status.file_status_code <> 'D'
-      AND video_file_status.file_status_code <> 'F'
-      AND video_file_status.file_status_code <> 'X'
+      AND video_event.locked_by IS NULL
 -- Process in priority, time order.
-ORDER BY priority DESC, recording_time ASC
+ORDER BY priority DESC, start_time ASC
 """)
-    jobs = []
-    for job_tuple in cur.fetchall():
+
+    for job_tuple in job_cur.fetchall():
         job = Job()
         jobs.append(job)
+        job.recordings = []
+
         # Convert tuple to named attributes.
         for i, name in enumerate(attrs):
             setattr(job, name.replace('.', '_'), job_tuple[i])
-        # Change interval strings to integers.
-        job.start_time = hms_to_seconds(job.start_time)
-        job.end_time = hms_to_seconds(job.end_time)
+
+        rec_cur.execute("""
+SELECT recording_filename, recording_time, start_time, end_time
+FROM (video_recording JOIN video_event_recording
+      ON video_recording.id = video_event_recording.recording_id)
+WHERE event_id = %d
+ORDER BY recording_time
+""",
+                        (job.video_event_event_id,))
+        for rec_tuple in rec_cur.fetchall():
+            rec = Recording()
+            job.recordings.append(rec)
+
+            rec.recording_filename = rec_tuple[0]
+            if not hasattr(job, 'recording_time'):
+                job.recording_time = rec_tuple[1]
+
+            # Change interval strings to integers.
+            rec.start_time = hms_to_seconds(rec_tuple[2])
+            rec.end_time = hms_to_seconds(rec_tuple[3])
+
     return jobs
 
 def mark_job_done(job, file_status_code):
     cur = database.get_cursor()
     cur.execute("""
-INSERT INTO video_target_file(event_recording_id, target_format_id,
+INSERT INTO video_target_file(event_id, target_format_id,
                               file_status_id, generated_time)
-VALUES(%(event_recording_id)s, %(target_format_id)s,
+VALUES(%(event_id)s, %(target_format_id)s,
        (SELECT id FROM video_file_status
         WHERE file_status_code=%(file_status_code)s),
        CURRENT_TIMESTAMP AT TIME ZONE 'GMT')
 """,
-                {'event_recording_id': job.video_event_recording_id,
+                {'event_id': job.video_event_event_id,
                  'target_format_id': job.video_target_format_id,
                  'file_status_code': file_status_code})
     cur.execute('COMMIT')
@@ -88,21 +107,16 @@
     # Generate target_filename.
     job.target_filename = debconfvideo.target_filename(job, job)
 
-    if os.path.exists(job.recording_filename):
+    if all(os.path.exists(rec.recording_filename) for rec in job.recordings):
         source_container_name = 'dv'
-        source_filename = job.recording_filename
-        start_time = job.start_time
-        end_time = job.end_time
+        source_command = make_source_command(job.recordings)
     else:
         archival_filename = '%s/archival/%s.ogg' % (
             config['FILE_BASE'],
-            job.event_recording_base_name)
+            job.event_base_name)
         if os.path.exists(archival_filename):
             source_container_name = 'ogg'
-            source_filename = archival_filename
-            # archival version is already cut
-            start_time = None
-            end_time = None
+            source_command = argv_to_command_line(['cat', archival_filename])
         else:
             return False
 
@@ -116,11 +130,7 @@
         == ('ogg', 'theora', 'vorbis')):
         argv = ['ffmpeg2theora']
         # input, output
-        argv.extend(['-f', source_container_name, source_filename])
-        if start_time:
-            argv.extend(['-s', str(start_time)])
-        if end_time:
-            argv.extend(['-e', str(end_time)])
+        argv.extend(['-f', source_container_name, '/dev/stdin'])
         argv.extend(['-o', job.target_filename])
         # video parameters
         if job.video_width and job.video_height:
@@ -145,11 +155,7 @@
     else:
         argv = ['ffmpeg']
         # input, output
-        argv.extend(['-f', source_container_name, '-i', source_filename])
-        if start_time:
-            argv.extend(['-ss', str(start_time)])
-        if end_time:
-            argv.extend(['-t', str(end_time - (start_time or 0))])
+        argv.extend(['-f', source_container_name, '-i', '/dev/stdin'])
         if re.match(r'(?:pal|ntsc|film)-', job.container_name):
             argv.extend(['-target', job.container_name])
         else:
@@ -176,7 +182,9 @@
         # output filename must come after all the output options
         argv.extend(['-y', job.target_filename])
 
-    job.command = argv_to_command_line(argv) + ' >%s.log 2>&1' % job.target_filename
+    job.command = ('%s 2>/dev/null | %s >%s.log 2>&1 && test ${PIPESTATUS[0]} = 0'
+                   % (source_command, argv_to_command_line(['nice'] + argv),
+                      job.target_filename))
     return True
 
 def argv_to_command_line(argv):
@@ -188,6 +196,19 @@
             return "'" + arg.replace("'", "'\\''") + "'"
     return ' '.join(escape(arg) for arg in argv)
 
+def make_source_command(recordings):
+    argvv = []
+    for rec in recordings:
+        argv = ['ffmpeg', '-f', 'dv', '-i', rec.recording_filename]
+        if rec.start_time:
+            argv.extend(['-ss', str(rec.start_time)])
+        if rec.end_time:
+            argv.extend(['-t', str(rec.end_time - (rec.start_time or 0))])
+        argv.extend(['-f', 'dv', '-vcodec', 'copy', '-acodec', 'copy',
+                     '-y', '/dev/stdout'])
+        argvv.append(argv)
+    return '(' + ' && '.join(argv_to_command_line(argv) for argv in argvv) + ')'
+
 def main(pretend=False):
     jobs_by_host_proc = {}
     jobs_by_key = {}
@@ -200,7 +221,7 @@
         # Generate a queue of jobs.  Remove the jobs we're running.
         queue = [job
                  for job in get_job_queue()
-                 if (job.video_event_recording_id,
+                 if (job.video_event_event_id,
                      job.video_target_format_id) not in jobs_by_key]
         queue_pos = 0
 
@@ -227,9 +248,9 @@
                                % (job.target_filename, job.command, host))
                         job.pipe = os.popen(
                             argv_to_command_line(
-                            ['ssh', host, 'nice', job.command]))
+                            ['ssh', host, job.command]))
                         jobs_by_host_proc[host_proc] = job
-                        jobs_by_key[(job.video_event_recording_id,
+                        jobs_by_key[(job.video_event_event_id,
                                      job.video_target_format_id)] = job
                         break
                     else:
@@ -274,7 +295,7 @@
                         # EOF = job is done
                         result = job.pipe.close()
                         del jobs_by_host_proc[host_proc]
-                        del jobs_by_key[(job.video_event_recording_id,
+                        del jobs_by_key[(job.video_event_event_id,
                                          job.video_target_format_id)]
                         # If command succeeded and the target file exists,
                         # give it unknown status.  Otherwise immediately

Modified: package/branches/pycon09/src/dc-video-schema.sql
===================================================================
--- package/branches/pycon09/src/dc-video-schema.sql	2009-03-28 22:20:31 UTC (rev 414)
+++ package/branches/pycon09/src/dc-video-schema.sql	2009-03-29 18:12:28 UTC (rev 415)
@@ -33,6 +33,13 @@
     FOREIGN KEY (conference_id, conference_room) REFERENCES conference_room
 );
 
+-- Extra event details needed for video.
+CREATE TABLE video_event (
+    event_id integer PRIMARY KEY REFERENCES event,
+    event_base_name character varying(150) NOT NULL UNIQUE,
+    locked_by integer
+);
+
 -- Status (quality) of a video file.
 CREATE TABLE video_file_status (
     id serial PRIMARY KEY,
@@ -61,12 +68,11 @@
 -- single file.
 CREATE TABLE video_event_recording (
     id serial PRIMARY KEY,
-    event_id integer NOT NULL REFERENCES event,
+    event_id integer NOT NULL REFERENCES video_event,
     recording_id integer NOT NULL REFERENCES video_recording,
     -- Start and end of the event recording within the file.
     start_time interval(3) NOT NULL,
     end_time interval(3) NOT NULL,
-    event_recording_base_name character varying(150) NOT NULL UNIQUE,
     UNIQUE (event_id, recording_id),
     CHECK (start_time < end_time)
 );
@@ -101,7 +107,7 @@
 -- from other relations.
 CREATE TABLE video_target_file (
     id serial PRIMARY KEY,
-    event_recording_id integer NOT NULL REFERENCES video_event_recording,
+    event_id integer NOT NULL REFERENCES video_event,
     target_format_id integer NOT NULL REFERENCES video_target_format,
     file_status_id integer NOT NULL REFERENCES video_file_status,
     comments text,

Modified: package/branches/pycon09/src/debconfvideo/__init__.py
===================================================================
--- package/branches/pycon09/src/debconfvideo/__init__.py	2009-03-28 22:20:31 UTC (rev 414)
+++ package/branches/pycon09/src/debconfvideo/__init__.py	2009-03-29 18:12:28 UTC (rev 415)
@@ -4,9 +4,9 @@
 
 config = shellconfig.read_file('/etc/default/debconf-video')
 
-def target_filename(video_event_recording, video_target_format):
+def target_filename(video_event, video_target_format):
     return ('%s/%s/%s%s' %
             (config['FILE_BASE'],
              video_target_format.target_format_abbr,
-             video_event_recording.event_recording_base_name,
+             video_event.event_base_name,
              video_target_format.filename_extension))

Modified: package/branches/pycon09/src/pyconvideo/pyconvideo/admin.py
===================================================================
--- package/branches/pycon09/src/pyconvideo/pyconvideo/admin.py	2009-03-28 22:20:31 UTC (rev 414)
+++ package/branches/pycon09/src/pyconvideo/pyconvideo/admin.py	2009-03-29 18:12:28 UTC (rev 415)
@@ -17,7 +17,7 @@
     ordering = ['recording_time']
 
 class VideoTargetFileAdmin(admin.ModelAdmin):
-    fields = ['event_recording', 'target_format',
+    fields = ['event', 'target_format',
               'generated_time', 'file_status', 'comments', 'locked_by',
               'published_time']
     list_display = ['target_filename', 'file_status', 'locked_by']
@@ -32,3 +32,4 @@
 admin.site.register(models.VideoTargetFormat)
 admin.site.register(models.VideoTargetFile, VideoTargetFileAdmin)
 admin.site.register(models.VideoEncodingHost)
+admin.site.register(models.VideoEvent)

Modified: package/branches/pycon09/src/pyconvideo/pyconvideo/models.py
===================================================================
--- package/branches/pycon09/src/pyconvideo/pyconvideo/models.py	2009-03-28 22:20:31 UTC (rev 414)
+++ package/branches/pycon09/src/pyconvideo/pyconvideo/models.py	2009-03-29 18:12:28 UTC (rev 415)
@@ -62,6 +62,18 @@
     class Meta:
         db_table = u'event'
 
+class VideoEvent(models.Model):
+    event = models.ForeignKey(Event, primary_key=True)
+    event_base_name = \
+        models.SlugField(max_length=150, unique=True,
+                         verbose_name='base name for target files')
+    locked_by = models.ForeignKey(auth.models.User, db_column='locked_by',
+                                  blank=True, null=True)
+    def __unicode__(self):
+        return unicode(self.event)
+    class Meta:
+        db_table = u'video_event'
+
 class VideoFileStatus(models.Model):
     id = models.AutoField(primary_key=True)
     file_status_code = models.CharField(unique=True, max_length=1,
@@ -85,9 +97,10 @@
     recording_filename = models.CharField(unique=True, max_length=100,
                                           verbose_name='filename')
     file_status = models.ForeignKey(VideoFileStatus)
-    comments = models.TextField()
-    locked_by = models.ForeignKey(auth.models.User, db_column='locked_by', null=True)
-    event_set = models.ManyToManyField(Event, related_name='recording_set',
+    comments = models.TextField(blank=True, null=True)
+    locked_by = models.ForeignKey(auth.models.User, db_column='locked_by',
+                                  blank=True, null=True)
+    event_set = models.ManyToManyField(VideoEvent, related_name='recording_set',
                                        through='VideoEventRecording')
     def __unicode__(self):
         return self.recording_filename
@@ -96,13 +109,10 @@
 
 class VideoEventRecording(models.Model):
     id = models.AutoField(primary_key=True)
-    event = models.ForeignKey(Event)
+    event = models.ForeignKey(VideoEvent)
     recording = models.ForeignKey(VideoRecording)
     start_time = IntervalField(precision=2)
     end_time = IntervalField(precision=2)
-    event_recording_base_name = \
-        models.SlugField(max_length=150, unique=True,
-                         verbose_name='base name for target files')
     def __unicode__(self):
         return u'"%s" in "%s"' % (self.event, self.recording)
     class Meta:
@@ -130,16 +140,16 @@
 
 class VideoTargetFile(models.Model):
     id = models.AutoField(primary_key=True)
-    event_recording = models.ForeignKey(VideoEventRecording,
-                                        related_name='target_set')
+    event = models.ForeignKey(VideoEvent, related_name='target_set')
     target_format = models.ForeignKey(VideoTargetFormat)
     file_status = models.ForeignKey(VideoFileStatus)
     comments = models.TextField()
-    locked_by = models.ForeignKey(auth.models.User, db_column='locked_by', null=True)
+    locked_by = models.ForeignKey(auth.models.User, db_column='locked_by',
+                                  blank=True, null=True)
     generated_time = models.DateTimeField()
     published_time = models.DateTimeField()
     def _target_filename(self):
-        return debconfvideo.target_filename(self.event_recording,
+        return debconfvideo.target_filename(self.event,
                                             self.target_format)
     target_filename = property(_target_filename)
     def __unicode__(self):

Modified: package/branches/pycon09/src/pyconvideo/pyconvideo/review_event_recording.html
===================================================================
--- package/branches/pycon09/src/pyconvideo/pyconvideo/review_event_recording.html	2009-03-28 22:20:31 UTC (rev 414)
+++ package/branches/pycon09/src/pyconvideo/pyconvideo/review_event_recording.html	2009-03-29 18:12:28 UTC (rev 415)
@@ -28,8 +28,12 @@
     <form action="review" method="POST">
       <input type="hidden" name="event" value="{{ event.event_id }}" />
       {{ form.as_p }}
-      <input type="submit" name="finish" value="Finish review" /> |
-      <input type="submit" name="continue" value="Continue review" />
+      <input type="submit" name="finish" value="Finish review" />
+      if recording and event are both completely accounted for<br />
+      <input type="submit" name="finish-recording" value="Finish recording" />
+      if event has another recording<br />
+      <input type="submit" name="finish-event" value="Finish event" />
+      if recording has another event
     </form>
   </body>
 

Modified: package/branches/pycon09/src/pyconvideo/pyconvideo/videorecording_list.html
===================================================================
--- package/branches/pycon09/src/pyconvideo/pyconvideo/videorecording_list.html	2009-03-28 22:20:31 UTC (rev 414)
+++ package/branches/pycon09/src/pyconvideo/pyconvideo/videorecording_list.html	2009-03-29 18:12:28 UTC (rev 415)
@@ -47,7 +47,7 @@
 	<td align="center"><B><BIG>{{ recording.file_status.file_status_code }}</BIG></B></td>
 	<td>
 	  {% for event in recording.event_set.all %}
-	  {{ event.title }}<br />
+	  {{ event.event.title }}<br />
 	  {% endfor %}
 	  {% if recording.locked_by %}
 	  {% ifequal recording.locked_by user %}

Modified: package/branches/pycon09/src/pyconvideo/pyconvideo/views.py
===================================================================
--- package/branches/pycon09/src/pyconvideo/pyconvideo/views.py	2009-03-28 22:20:31 UTC (rev 414)
+++ package/branches/pycon09/src/pyconvideo/pyconvideo/views.py	2009-03-29 18:12:28 UTC (rev 415)
@@ -86,9 +86,32 @@
                                    'user': recording.locked_by})
 
     event_id = request.POST.get('event')
-    event = event_id and models.Event.objects.get(event_id=event_id)
+    if event_id:
+        event = models.Event.objects.get(event_id=event_id)
+        try:
+            video_event = models.VideoEvent.objects.get(event=event)
+        except models.VideoEvent.DoesNotExist:
+            video_event = None
+    else:
+        event = None
+        video_event = None
 
-    if request.POST.get('finish') or request.POST.get('continue'):
+    if video_event:
+        # lock or fail
+        if not video_event.locked_by:
+            video_event.locked_by = request.user
+            video_event.save()
+        elif video_event.locked_by == request.user:
+            pass
+        else:
+            # XXX not really an appropriate template
+            return render_to_response('pyconvideo/review_locked.html',
+                                      {'filename': event.title,
+                                       'user': video_event.locked_by})
+
+    if (request.POST.get('finish') or
+        request.POST.get('finish-recording') or
+        request.POST.get('finish-event')):
         # process review
         form = RecordingReviewForm(request.POST)
         if form.is_valid():
@@ -98,21 +121,27 @@
                 models.VideoFileStatus.objects.get(
                 file_status_code=data['file_status'])
             recording.comments = data['comments']
-            if request.POST.get('finish'):
+            if request.POST.get('finish') or request.POST.get('finish-recording'):
                 recording.locked_by = None
             recording.save()
 
+            if not video_event:
+                video_event = models.VideoEvent(event=event)
+            video_event.event_base_name = data['base_name']
+            if request.POST.get('finish') or request.POST.get('finish-event'):
+                video_event.locked_by = None
+            video_event.save()
+
             try:
                 event_recording = \
                     models.VideoEventRecording.objects.get(
-                    event=event, recording=recording)
+                    event=video_event, recording=recording)
             except models.VideoEventRecording.DoesNotExist:
                 event_recording = \
                     models.VideoEventRecording(
-                    event=event, recording=recording)
+                    event=video_event, recording=recording)
             event_recording.start_time = data['start_time']
             event_recording.end_time = data['end_time']
-            event_recording.event_recording_base_name = data['base_name']
             event_recording.save()
 
             if request.POST.get('finish'):
@@ -138,6 +167,10 @@
         # show main form
         data = {'file_status': recording.file_status.file_status_code,
                 'comments': recording.comments}
+        if video_event:
+            data['base_name'] = video_event.event_base_name
+        else:
+            data['base_name'] = slugify(event.title)
         try:
             event_recording = \
                 models.VideoEventRecording.objects.get(
@@ -145,11 +178,9 @@
         except models.VideoEventRecording.DoesNotExist:
             data['start_time'] = '00:00:00'
             data['end_time'] = recording.recording_duration
-            data['base_name'] = slugify(event.title)
         else:
             data['start_time'] = event_recording.start_time
             data['end_time'] = event_recording.end_time
-            data['base_name'] = event_recording.event_recording_base_name
         return render_to_response('pyconvideo/review_event_recording.html',
                                   {'recording': recording,
                                    'event': event,
@@ -170,7 +201,7 @@
                                'events': events})
 
 def list_targets(request):
-    sources = models.VideoEventRecording.objects.all().select_related()
+    sources = models.VideoEvent.objects.all().select_related()
     formats = models.VideoTargetFormat.objects.all()
     files = []
     for source in sources:




More information about the Debconf-video-commits mailing list