_record_chunks.tcl 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. namespace eval record_chunks {
  2. set_help_text record_chunks \
  3. {Records videos in doublesize format, but has extra options to record for a
  4. limited amount of time and to chop up videos in chunks of a certain number of
  5. seconds.
  6. Usage:
  7. record_chunks [-chunktime <chunktime>] [-totaltime <totaltime> | -numchunks <numchunks>] start filename
  8. record_chunks stop
  9. The chunktime parameter controls the maximum time for each chunk (default:
  10. 14:59 for YouTube) in seconds and the totaltime parameter controls the total
  11. time to record in seconds (default: infinite). Instead of the totalchunks
  12. parameter, you can also use the numchunks parameter to control the total time
  13. as a multiple of the chunk time. The options are mutually exclusive.
  14. The chunks are recorded with a chunk-number suffix behind filename. Do not use
  15. an extension with filename, just a name is enough. Always provide the options
  16. first.
  17. Examples:
  18. record_chunks -numchunks 1 start simplegame
  19. Records a movie of 14:59 (maximum time for YouTube) to simplegame.avi
  20. record_chunks start longgame
  21. Records an infinite number of movies of 14:59 (maximum time for
  22. YouTube), until you use record_chunks stop. Names are longname01.avi,
  23. longname02.avi, etc.
  24. record_chunks -chunktime 60 -totaltime 230 start partgame
  25. Records movies of 1 minute until the total recorded time is 3:50, names
  26. are partgame01.avi, partgame02.avi, partgame03.avi and partgame04.avi.
  27. }
  28. variable filenamebase
  29. variable iteration
  30. variable chunk_time
  31. variable total_time
  32. variable next_after_id
  33. proc record_chunks {args} {
  34. variable filenamebase
  35. variable iteration
  36. variable chunk_time
  37. variable total_time
  38. variable next_after_id
  39. # the defaults
  40. set chunk_time 899 ;# max time for a YouTube video
  41. set total_time -1 ;# record until someone says stop ..
  42. set num_chunks -1 ;# .. or till we recorded this many chunks
  43. while (1) {
  44. switch -- [lindex $args 0] {
  45. "-chunktime" {
  46. set chunk_time [lindex $args 1]
  47. set args [lrange $args 2 end]
  48. }
  49. "-totaltime" {
  50. set total_time [lindex $args 1]
  51. set args [lrange $args 2 end]
  52. }
  53. "-numchunks" {
  54. set num_chunks [lindex $args 1]
  55. set args [lrange $args 2 end]
  56. }
  57. "default" {
  58. break
  59. }
  60. }
  61. }
  62. if {($total_time > 0) && ($num_chunks > 0)} {
  63. error "You can't use both -numchunks and -totaltime options at the same time."
  64. }
  65. # do this outside of the loop, so that the order of options isn't too strict
  66. if {$num_chunks > 0} {
  67. set total_time [expr {$num_chunks * $chunk_time}]
  68. }
  69. switch -- [lindex $args 0] {
  70. "start" {
  71. if {[llength $args] != 2} {
  72. error "Expected another argument: the name of your video!"
  73. }
  74. if {[dict get [record status] status] ne "idle"} {
  75. error "Already recording!"
  76. }
  77. set filenamebase [lindex $args 1]
  78. set iteration 0
  79. record_next_part
  80. }
  81. "stop" {
  82. if {[llength $args] != 1} {
  83. error "Too many arguments. Stop is just stop."
  84. }
  85. if {![info exists record_chunks::iteration]} {
  86. error "No recording in progress..."
  87. }
  88. after cancel $next_after_id
  89. stop_recording
  90. }
  91. "default" {
  92. error "Syntax error in command."
  93. }
  94. }
  95. }
  96. proc stop_recording {} {
  97. variable iteration
  98. unset iteration
  99. record stop
  100. puts "Stopped recording..."
  101. }
  102. proc record_next_part {} {
  103. variable iteration
  104. variable next_after_id
  105. variable filenamebase
  106. variable chunk_time
  107. variable total_time
  108. set cmd record_chunks::record_next_part
  109. set time_to_record $chunk_time
  110. if {$total_time > 0} {
  111. set time_left [expr {$total_time - ($chunk_time * $iteration)}]
  112. if {$time_left <= $chunk_time} {
  113. set cmd record_chunks::stop_recording
  114. set time_to_record $time_left
  115. }
  116. }
  117. record stop
  118. incr iteration
  119. if {$iteration == 1 && $cmd eq "record_chunks::stop_recording"} {
  120. # if we're only going to record one movie, no need to number it
  121. set fname $filenamebase
  122. } else {
  123. set fname [format "%s%02d" $filenamebase $iteration]
  124. }
  125. record start -doublesize $fname
  126. set next_after_id [after time $time_to_record $cmd]
  127. puts "Recording to $fname for [utils::format_time $time_to_record]..."
  128. }
  129. namespace export record_chunks
  130. set_help_text record_chunks_on_framerate_changes \
  131. {Records videos as with the normal record command, but starts recording to a
  132. new video file if the framerate of the VDP changes.
  133. Note that this will only work if you use either use the -prefix option for the
  134. command, or the automatic file name generation. If you specify an exact
  135. filename without the -prefix option, openMSX will record all chunks to the same
  136. file.
  137. Stop recording with the "record_chunks_on_framerate_changes stop" command.
  138. Examples:
  139. record_chunks_on_framerate_changes start -triplesize -prefix UR
  140. Records a movie in triplesize in files UR0001.avi, UR0002.avi etc.
  141. Each subsequent AVI has a different frame rate.
  142. record_chunks_on_framerate_changes stop
  143. Stop recording and clean up frame rate detection stuff internally.
  144. }
  145. variable vdp_write_watchpoint ""
  146. variable after_reset_id ""
  147. variable prev_was_ntsc_mode true
  148. variable record_args ""
  149. proc vdp_write_check {} {
  150. variable prev_was_ntsc_mode
  151. set safety_check_output [safety_check]
  152. if {$safety_check_output ne ""} {
  153. puts $safety_check_output
  154. return
  155. }
  156. set ntsc_mode [expr {([vdpreg 9] & 2) == 0}]
  157. if {$ntsc_mode != $prev_was_ntsc_mode} {
  158. restart_recording "from [expr {$prev_was_ntsc_mode ? 60 : 50}] to [expr {$ntsc_mode ? 60 : 50}] Hz"
  159. set prev_was_ntsc_mode $ntsc_mode
  160. }
  161. }
  162. proc safety_check {} {
  163. if {[dict get [record status] status] eq "idle"} {
  164. # oh, we weren't even recording... let's shut down
  165. disable_monitoring
  166. return "Stopped recording chunks on frame rate changes. I found out late, please use record_chunks_on_framerate_changes stop next time, so I'll find out sooner..."
  167. }
  168. return ""
  169. }
  170. proc restart_recording { reason } {
  171. variable record_args
  172. puts "Frame rate change detected $reason. Starting recording to next video file."
  173. record stop
  174. puts [eval [linsert $record_args 0 record]]
  175. }
  176. proc on_reset {} {
  177. variable after_reset_id
  178. set after_reset_id [after boot [namespace code on_reset]]
  179. set safety_check_output [safety_check]
  180. if {$safety_check_output ne ""} {
  181. puts $safety_check_output
  182. return
  183. }
  184. restart_recording "due to a reset"
  185. }
  186. proc disable_monitoring {} {
  187. variable vdp_write_watchpoint
  188. variable after_reset_id
  189. debug remove_watchpoint $vdp_write_watchpoint
  190. set vdp_write_watchpoint ""
  191. after cancel $after_reset_id
  192. set after_reset_id ""
  193. }
  194. proc record_chunks_on_framerate_changes { args } {
  195. variable record_args
  196. variable vdp_write_watchpoint
  197. variable after_reset_id
  198. variable prev_was_ntsc_mode
  199. if {$args eq "stop" && $vdp_write_watchpoint ne ""} {
  200. disable_monitoring
  201. record stop
  202. return "Stopped recording chunks on framerate changes."
  203. }
  204. set record_args $args
  205. set response [eval [linsert $record_args 0 record]]
  206. if {$vdp_write_watchpoint eq "" && [dict get [record status] status] eq "recording"} {
  207. set prev_was_ntsc_mode [expr {([vdpreg 9] & 2) == 0}]
  208. set vdp_write_watchpoint [debug set_watchpoint write_io 0x99 1 {
  209. record_chunks::vdp_write_check
  210. }]
  211. set after_reset_id [after boot [namespace code on_reset]]
  212. return "Started recording chunks on framerate changes...\n$response"
  213. }
  214. }
  215. namespace export record_chunks_on_framerate_changes
  216. } ;# namespace record_chunks
  217. namespace import record_chunks::*