Advertisement
Guest User

QP file from Matroska chapter file (IDR).py

a guest
Mar 1st, 2014
347
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.70 KB | None | 0 0
  1. # -*- coding: utf-8 -*-
  2.  
  3. """
  4. Create a x264 QP file from a Matroska chapter file
  5.  
  6. This macro takes a Matroska chapter file, gets the starting time
  7. of every chapter and writes a QP file marking those frames as key
  8. frames.
  9.  
  10. The frame rate of the video is needed.  A constant fps value can be
  11. obtained from the script in the current tab or introduced directly.
  12. Timecodes format v1 and v2 files are also accepted.
  13.  
  14. Current issues:
  15. - Unnecessary key frames may be added with some ordered chapters
  16.  
  17.  
  18. Date: 2013-07-03
  19. Latest version:  https://github.com/vdcrim/avsp-macros
  20.  
  21.  
  22. Copyright (C) 2013  Diego Fernández Gosende <dfgosende@gmail.com>
  23.  
  24. This program is free software: you can redistribute it and/or modify
  25. it under the terms of the GNU General Public License as published by
  26. the Free Software Foundation, either version 2 of the License, or
  27. (at your option) any later version.
  28.  
  29. This program is distributed in the hope that it will be useful,
  30. but WITHOUT ANY WARRANTY; without even the implied warranty of
  31. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  32. GNU General Public License for more details.
  33.  
  34. You should have received a copy of the GNU General Public License along
  35. with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
  36.  
  37. """
  38.  
  39. # PREFERENCES
  40.  
  41. # Suffix list for automatic search of a chapter and timecode file
  42. chapters_suffix = ['_Chapters.xml', '.chapters.xml', '.xml']
  43. tc_suffix = ['.otc.txt', '.tc.txt', '.timecode.txt', '.timecodes.txt', '.txt']
  44.  
  45.  
  46. # ------------------------------------------------------------------------------
  47.  
  48.  
  49. # run in thread
  50. import os.path
  51. import re
  52.  
  53. def time2ms(time):
  54.     return ((time[0] * 60 + time[1]) * 60 + time[2]) * 1000 + time[3] / 10**6
  55.  
  56. def timecode_v1_to_v2(lines, offset=0, start=0, end_frame=None, end_ms=None,
  57.                       default=24/1.001, float_list=False):
  58.     """Convert a timecode v1 file to v2
  59.    
  60.    lines: list of lines of the timecode v1 file (excluding header)
  61.    offset:    starting time (ms)
  62.    start:     first frame
  63.    end_frame: last frame
  64.    end_ms:    last timecode
  65.    default:   FPS used if 'assume' line isn't present
  66.    
  67.    Returns the list of timecode v2 lines (str)
  68.    
  69.    """
  70.     # Generate all intervals
  71.     inters = []
  72.     all_inters =[]
  73.     try:
  74.         default = float(lines[0].split()[1])
  75.         i = 1
  76.     except IndexError:
  77.         i = 0
  78.     inters = [[int(line[0]), int(line[1]), float(line[2])] for line in
  79.                                 [line.strip().split(',') for line in lines[i:]]]
  80.     if start < inters[0][0]:
  81.         all_inters.append([start, inters[0][0] - 1, default])
  82.     try:
  83.         for i, inter in enumerate(inters):
  84.             all_inters.append(inter)
  85.             if inters[i+1][0] - inter[1] > 1:
  86.                 all_inters.append([inter[1] + 1, inters[i+1][0] - 1, default])
  87.     except IndexError:
  88.         if end_frame is not None and end_frame > inters[-1][1]:
  89.             all_inters.append([inters[-1][1] + 1, end_frame, default])
  90.    
  91.     # v1 -> v2
  92.     v2 = [] if offset else [0.0]
  93.     for inter in all_inters:
  94.         ms = 1000.0 / inter[2]
  95.         for i in range(1, inter[1] - inter[0] + 2):
  96.             v2.append(ms * i + offset)
  97.         offset = v2[-1]
  98.     if end_ms is not None:
  99.         i = 1
  100.         while v2[-1] < end_ms:
  101.             v2.append(ms * i + offset)
  102.             i += 1
  103.     if float_list:
  104.         return v2
  105.     return ['{0:.3f}\n'.format(tc) for tc in v2]
  106.  
  107. # Ask for options
  108. avs = avsp.GetScriptFilename()
  109. chapters_path = tc_path = qpfile_path = ''
  110. if avs:
  111.     avs_no_ext = os.path.splitext(avs)[0]
  112.     for path in (avs_no_ext + suffix for suffix in chapters_suffix):
  113.         if os.path.isfile(path):
  114.             chapters_path = path
  115.             break
  116.     for path in (avs_no_ext + suffix for suffix in tc_suffix):
  117.         if os.path.isfile(path):
  118.             tc_path = path
  119.             break
  120.     qpfile_path = avs_no_ext + '.qpf'
  121. fps_str = avsp.Options.get('fps', '24')
  122. fps_from_script = avsp.Options.get('fps_from_script', False)
  123. xml_filter = (_('XML files') + ' (*.xml)|*.xml|' + _('All files') + '|*.*')
  124. tc_filter = (_('Text files') + ' (*.txt)|*.txt|' + _('All files') + '|*.*')
  125. qpf_filter = (_('QP files') + ' (*.qp;*.qpf;*.qpfile)|*.qp;*.qpf;*.qpfile|' +
  126.               _('All files') + '|*.*')
  127. while True:
  128.     options = avsp.GetTextEntry(
  129.         title=_('QP file from Matroska chapter file'),
  130.         message=[
  131.             _('Matroska chapter file'),
  132.             [_('Frame rate'), _('Get the FPS from the script')],
  133.             _('Use a timecode file (v1 or v2) instead'),
  134.             _('Output QP file (blank -> same name as chapter file)'),
  135.             ],
  136.         default=[
  137.             (chapters_path, xml_filter),
  138.             [('23.976', '24', '25', '29.970', '30', '50', '59.940', fps_str),
  139.              fps_from_script],
  140.             (tc_path, tc_filter),
  141.             (qpfile_path, qpf_filter)
  142.             ],
  143.         types=[
  144.             'file_open', ['list_writable', 'check'], 'file_open', 'file_save'])
  145.     if not options:
  146.         return
  147.     chapters_path, fps_str, fps_from_script, tc_path, qpfile_path = options
  148.     if not chapters_path:
  149.         avsp.MsgBox(_('A chapter file is needed!'), _('Error'))
  150.         continue
  151.     if tc_path:
  152.         cfr = False
  153.     else:
  154.         if fps_from_script:
  155.             fps = avsp.GetVideoFramerate()
  156.             if not fps:
  157.                 return
  158.         else:
  159.             if fps_str == '23.976':
  160.                 fps = float(24/1.001)
  161.             elif fps_str == '29.970':
  162.                 fps = float(30/1.001)
  163.             elif fps_str == '59.940':
  164.                 fps = float(60/1.001)
  165.             else:
  166.                 try:
  167.                     fps = float(fps_str)
  168.                 except:
  169.                     avsp.MsgBox(_('Incorrect frame rate'), _('Error'))
  170.                     continue
  171.         cfr = True
  172.     break
  173. avsp.Options['fps'] = fps_str
  174. avsp.Options['fps_from_script'] = fps_from_script
  175. if not qpfile_path:
  176.     qpfile_path = os.path.splitext(chapters_path)[0] + '.qpf'
  177.  
  178. # Get chapter start times
  179. re_chapters = re.compile(ur'^.*<ChapterTimeStart>\s*(\d+):(\d+):(\d+)\.(\d+)'
  180.                          ur'\s*</ChapterTimeStart>.*$')
  181. chapters_ms = set()
  182. with open(chapters_path) as file:
  183.     for line in file:
  184.         chapter = re_chapters.search(line)
  185.         if chapter:
  186.             chapters_ms.add(time2ms([int(g) for g in chapter.groups()]))
  187. chapters_ms = sorted(chapters_ms)
  188. if not chapters_ms[0]:
  189.     del chapters_ms[0]
  190. if not chapters_ms:
  191.     avsp.MsgBox(_('No chapter starting times in file!'), _('Error'))
  192.     return
  193.  
  194. # Convert ms to frame number
  195. frames = []
  196. if cfr:
  197.     for ms in chapters_ms:
  198.         frames.append(int(round(ms * fps / 1000)))
  199. else:
  200.     # Read timecode file. Convert v1 -> v2
  201.     with open(tc_path) as itc:
  202.         header = itc.readline().strip()
  203.         if header == '# timecode format v2':
  204.             tcs = [float(line) for line in itc.readlines()]    
  205.         elif header == '# timecode format v1':
  206.             tcs = timecode_v1_to_v2(itc.readlines(), end_ms=chapters_ms[-1],
  207.                                     float_list=True)
  208.         else:
  209.             avsp.MsgBox(_('Invalid timecode file'), _('Error'))
  210.             return
  211.    
  212.     j = 0
  213.     for i, tc in enumerate(tcs):
  214.         if tc >= chapters_ms[j]:
  215.             if abs(tc - chapters_ms[j]) < abs(tcs[i - 1] - chapters_ms[j]):
  216.                 frames.append(i)
  217.             else:
  218.                 frames.append(i - 1)
  219.             if j + 1 == len(chapters_ms):
  220.                 break
  221.             j += 1
  222.         i += 1
  223.  
  224. # Save to file
  225. with open(qpfile_path, 'w') as f:
  226.     f.writelines(['{0} I\n'.format(frame) for frame in frames])
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement