[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